Skip to content

Instantly share code, notes, and snippets.

@CloudEllie
Last active May 23, 2023 12:53
Show Gist options
  • Save CloudEllie/213965a3448230f5b615e7046f9dd26d to your computer and use it in GitHub Desktop.
Save CloudEllie/213965a3448230f5b615e7046f9dd26d to your computer and use it in GitHub Desktop.
Automated findings for EigenLayer Code4rena audit - IllIllI-bot Bot Race submission

Summary

Medium Risk Issues

Issue Instances
[M‑01] Contracts are vulnerable to fee-on-transfer-token-related accounting issues 1

Total: 1 instances over 1 issues

Low Risk Issues

Issue Instances
[L‑01] Events are missing sender information 6
[L‑02] The owner is a single point of failure and a centralization risk 27
[L‑03] Use Ownable2Step's transfer function rather than Ownable's for transfers of ownership 2
[L‑04] Unsafe downcast 3
[L‑05] Loss of precision 4
[L‑06] Upgradeable contract not initialized 6
[L‑07] Array lengths not checked 3
[L‑08] Missing checks for address(0x0) when assigning values to address state variables 1
[L‑09] abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256() 2
[L‑10] Use Ownable2Step rather than Ownable 3

Total: 57 instances over 10 issues

Non-critical Issues

Issue Instances
[N‑01] Constants in comparisons should appear on the left side 118
[N‑02] Variables need not be initialized to zero 15
[N‑03] Imports could be organized more systematically 13
[N‑04] Large numeric literals should use underscores for readability 14
[N‑05] Upgradeable contract is missing a __gap[50] storage variable to allow for new storage variables in later versions 1
[N‑06] Import declarations should import specific identifiers, rather than the whole file 71
[N‑07] Missing initializer modifier on constructor 1
[N‑08] Unused file 1
[N‑09] The nonReentrant modifier should occur before all other modifiers 9
[N‑10] Adding a return statement when the function defines a named return variable, is redundant 2
[N‑11] public functions not called by the contract should be declared external instead 1
[N‑12] constants should be defined rather than using magic numbers 56
[N‑13] Cast is more restrictive than the type of the variable being assigned 1
[N‑14] Numeric values having to do with time should use time units for readability 1
[N‑15] Use a more recent version of solidity 3
[N‑16] Expressions for constant values such as a call to keccak256(), should use immutable rather than constant 2
[N‑17] Constant redefined elsewhere 6
[N‑18] Inconsistent spacing in comments 24
[N‑19] Lines are too long 23
[N‑20] Variable names that consist of all capital letters should be reserved for constant/immutable variables 2
[N‑21] Typos 21
[N‑22] Misplaced SPDX identifier 1
[N‑23] File is missing NatSpec 1
[N‑24] NatSpec is incomplete 41
[N‑25] Event is missing indexed fields 23
[N‑26] Consider using delete rather than assigning zero to clear values 1
[N‑27] Avoid the use of sensitive terms 31
[N‑28] Contracts should have full test coverage 1
[N‑29] Large or complicated code bases should implement invariant tests 1
[N‑30] Function ordering does not follow the Solidity style guide 7
[N‑31] Contract does not follow the Solidity style guide's suggested layout ordering 7

Total: 499 instances over 31 issues

Gas Optimizations

Issue Instances Total Gas Saved
[G‑01] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate 1 -
[G‑02] State variables should be cached in stack variables rather than re-reading them from storage 15 1455
[G‑03] Multiple accesses of a mapping/array should use a local variable cache 11 462
[G‑04] <x> += <y> costs more gas than <x> = <x> + <y> for state variables 5 565
[G‑05] internal functions only called once can be inlined to save gas 4 80
[G‑06] Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement 5 425
[G‑07] <array>.length should not be looked up in every loop of a for-loop 4 12
[G‑08] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops 8 480
[G‑09] require()/revert() strings longer than 32 bytes cost extra gas 86 -
[G‑10] Optimize names to save gas 19 418
[G‑11] Using bools for storage incurs overhead 4 68400
[G‑12] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too) 4 20
[G‑13] Splitting require() statements that use && saves gas 1 3
[G‑14] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead 4 -
[G‑15] Using private rather than public for constants, saves gas 6 -
[G‑16] Division by two should use bit shifting 2 40
[G‑17] require() or revert() statements that check input arguments should be at the top of the function 1 -
[G‑18] Use custom errors rather than revert()/require() strings to save gas 88 -
[G‑19] Functions guaranteed to revert when called by normal users can be marked payable 36 756
[G‑20] Constructors can be marked payable 7 147

Total: 311 instances over 20 issues with 73263 gas saved

Gas totals use lower bounds of ranges and count two iterations of each for-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. The table above as well as its gas numbers do not include any of the excluded findings.

Medium Risk Issues

[M‑01] Contracts are vulnerable to fee-on-transfer-token-related accounting issues

Without measuring the balance before and after the transfer, there's no way to ensure that enough tokens were transferred, in the cases where the token has a fee-on-transfer mechanic. If there are latent funds in the contract, subsequent transfers will succeed.

There is one instance of this issue:

File: /src/contracts/core/StrategyManager.sol

661:         token.safeTransferFrom(msg.sender, address(strategy), amount);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L661

Low Risk Issues

[L‑01] Events are missing sender information

When an action is triggered based on a user's action, not being able to filter based on who triggered the action makes event processing a lot more cumbersome. Including the msg.sender the events of these types of action will make events much more useful to end users.

There are 6 instances of this issue:

File: src/contracts/pods/BeaconChainOracle.sol

113:             emit StateRootConfirmed(blockNumber, stateRoot);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/BeaconChainOracle.sol#L113-L113

File: src/contracts/middleware/example/HashThreshold.sol

92:          emit MessageCertified(message);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/example/HashThreshold.sol#L92-L92

File: script/M1_Deploy.s.sol

93:          emit log_named_uint("You are deploying on ChainID", chainId);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/script/M1_Deploy.s.sol#L93-L93

File: src/contracts/middleware/PaymentManager.sol

394          emit PaymentBreakdown(
395              operator, challenge.fromTaskNumber, challenge.toTaskNumber, challenge.amount1, challenge.amount2
396:             );

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/PaymentManager.sol#L394-L396

File: src/contracts/core/Slasher.sol

464:             emit MiddlewareTimesAdded(operator, _operatorToMiddlewareTimes[operator].length - 1, next.stalestUpdateBlock, next.latestServeUntilBlock);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/Slasher.sol#L464-L464

File: src/contracts/core/StrategyManager.sol

669:         emit Deposit(depositor, token, strategy, shares);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L669-L669

[L‑02] The owner is a single point of failure and a centralization risk

Having a single EOA as the only owner of contracts is a large centralization risk and a single point of failure. A single private key may be taken in a hack, or the sole holder of the key may become unable to retrieve the key when necessary. Consider changing to a multi-signature setup, or having a role-based authorization model.

There are 27 instances of this issue:

File: src/contracts/pods/BeaconChainOracle.sol

67       function setThreshold(uint256 _threshold) external onlyOwner {
68           _setThreshold(_threshold);
69:      }

76       function addOracleSigners(address[] memory _oracleSigners) external onlyOwner {
77           _addOracleSigners(_oracleSigners);
78:      }

85       function removeOracleSigners(address[] memory _oracleSigners) external onlyOwner {
86           for (uint256 i = 0; i < _oracleSigners.length;) {
87               if (isOracleSigner[_oracleSigners[i]]) {
88                   emit OracleSignerRemoved(_oracleSigners[i]);
89                   isOracleSigner[_oracleSigners[i]] = false;
90                   totalOracleSigners -= 1;
91               }
92               unchecked {
93                   ++i;
94               }
95           }
96:      }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/BeaconChainOracle.sol#L67-L69

File: src/contracts/pods/DelayedWithdrawalRouter.sol

100      function setWithdrawalDelayBlocks(uint256 newValue) external onlyOwner {
101          _setWithdrawalDelayBlocks(newValue);
102:     }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L100-L102

File: src/contracts/pods/EigenPodManager.sol

161      function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external onlyOwner {
162          _updateBeaconChainOracle(newBeaconChainOracle);
163:     }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L161-L163

File: src/contracts/operators/MerkleDelegationTerms.sol

53       function operatorWithdrawal(TokenAndAmount[] calldata tokensAndAmounts) external onlyOwner {
...
62:      }

65       function postMerkleRoot(bytes32 newRoot, uint256 height) external onlyOwner {
...
75:      }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/operators/MerkleDelegationTerms.sol#L53-L62

File: lib/openzeppelin-contracts/contracts/access/Ownable.sol

61       function renounceOwnership() public virtual onlyOwner {
62           _transferOwnership(address(0));
63:      }

69       function transferOwnership(address newOwner) public virtual onlyOwner {
70           require(newOwner != address(0), "Ownable: new owner is the zero address");
71           _transferOwnership(newOwner);
72:      }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts/contracts/access/Ownable.sol#L61-L63

File: lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol

66       function renounceOwnership() public virtual onlyOwner {
67           _transferOwnership(address(0));
68:      }

74       function transferOwnership(address newOwner) public virtual onlyOwner {
75           require(newOwner != address(0), "Ownable: new owner is the zero address");
76           _transferOwnership(newOwner);
77:      }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol#L66-L68

File: lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol

51       function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
52           proxy.changeAdmin(newAdmin);
53:      }

62       function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
63           proxy.upgradeTo(implementation);
64:      }

74       function upgradeAndCall(
75           TransparentUpgradeableProxy proxy,
76           address implementation,
77           bytes memory data
78       ) public payable virtual onlyOwner {
79           proxy.upgradeToAndCall{value: msg.value}(implementation, data);
80:      }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol#L51-L53

File: src/contracts/core/Slasher.sol

127      function resetFrozenStatus(address[] calldata frozenAddresses) external onlyOwner {
128          for (uint256 i = 0; i < frozenAddresses.length;) {
129              _resetFrozenStatus(frozenAddresses[i]);
130              unchecked {
131                  ++i;
132              }
133          }
134:     }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/Slasher.sol#L127-L134

File: script/whitelist/Staker.sol

27       function callAddress(address implementation, bytes memory data) external onlyOwner returns(bytes memory) {
...
47:      }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/script/whitelist/Staker.sol#L27-L47

File: src/contracts/core/StrategyManager.sol

482      function slashShares(
483          address slashedAddress,
484          address recipient,
485          IStrategy[] calldata strategies,
486          IERC20[] calldata tokens,
487          uint256[] calldata strategyIndexes,
488          uint256[] calldata shareAmounts
489      )
490          external
491          onlyOwner
492          onlyFrozen(slashedAddress)
493          nonReentrant
494      {
...
524      }
525:     

536      function slashQueuedWithdrawal(address recipient, QueuedWithdrawal calldata queuedWithdrawal, IERC20[] calldata tokens, uint256[] calldata indicesToSkip)
537          external
538          onlyOwner
539          onlyFrozen(queuedWithdrawal.delegatedAddress)
540          nonReentrant
541      {
...
579:     }

582      function setWithdrawalDelayBlocks(uint256 _withdrawalDelayBlocks) external onlyOwner {
583          _setWithdrawalDelayBlocks(_withdrawalDelayBlocks);
584:     }

587      function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner {
588          _setStrategyWhitelister(newStrategyWhitelister);
589:     }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L482-L525

File: lib/openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol

49       function upgradeTo(address newImplementation) public virtual onlyOwner {
50           _setImplementation(newImplementation);
51           emit Upgraded(newImplementation);
52:      }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol#L49-L52

File: script/whitelist/Whitelister.sol

49       function whitelist(address operator) public onlyOwner {
...
73:      }

95       function depositIntoStrategy(
96           address staker,
97           IStrategy strategy,
98           IERC20 token,
99           uint256 amount
100      ) public onlyOwner returns (bytes memory) {
101         
102          bytes memory data = abi.encodeWithSelector(
103                  IStrategyManager.depositIntoStrategy.selector,
104                  strategy,
105                  token,
106                  amount
107          );
108  
109          return Staker(staker).callAddress(address(strategyManager), data);
110:     }

112      function queueWithdrawal(
113          address staker,
114          uint256[] calldata strategyIndexes,
115          IStrategy[] calldata strategies,
116          uint256[] calldata shares,
117          address withdrawer,
118          bool undelegateIfPossible
119      ) public onlyOwner returns (bytes memory) {
120          bytes memory data = abi.encodeWithSelector(
121                  IStrategyManager.queueWithdrawal.selector,
122                  strategyIndexes,
123                  strategies,
124                  shares,
125                  withdrawer,
126                  undelegateIfPossible
127              );
128          return Staker(staker).callAddress(address(strategyManager), data);
129:     }

131      function completeQueuedWithdrawal(
132          address staker,
133          IStrategyManager.QueuedWithdrawal calldata queuedWithdrawal,
134          IERC20[] calldata tokens,
135          uint256 middlewareTimesIndex,
136          bool receiveAsTokens
137      ) public onlyOwner returns (bytes memory) {
138          bytes memory data = abi.encodeWithSelector(
139                  IStrategyManager.completeQueuedWithdrawal.selector,
140                  queuedWithdrawal,
141                  tokens,
142                  middlewareTimesIndex,
143                  receiveAsTokens
144          );
145  
146          return Staker(staker).callAddress(address(strategyManager), data);
147:     }

149      function transfer(
150          address staker,
151          address token,
152          address to,
153          uint256 amount
154      ) public onlyOwner returns (bytes memory) {
155          bytes memory data = abi.encodeWithSelector(IERC20.transfer.selector, to, amount);
156  
157          return Staker(staker).callAddress(token, data);
158:     }

160      function callAddress(
161          address to,
162          bytes memory data
163      ) public onlyOwner payable returns (bytes memory) {
164          (bool ok, bytes memory res) = payable(to).call{value: msg.value}(data);
165          if (!ok) {
166              revert(string(res));
167          }
168          return res;
169:     }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/script/whitelist/Whitelister.sol#L49-L73

[L‑03] Use Ownable2Step's transfer function rather than Ownable's for transfers of ownership

Ownable2Step and Ownable2StepUpgradeable prevent the contract ownership from mistakenly being transferred to an address that cannot handle it (e.g. due to a typo in the address), by requiring that the recipient of the owner permissions actively accept via a contract call of its own.

There are 2 instances of this issue:

File: lib/openzeppelin-contracts/contracts/access/Ownable.sol

69       function transferOwnership(address newOwner) public virtual onlyOwner {
70           require(newOwner != address(0), "Ownable: new owner is the zero address");
71           _transferOwnership(newOwner);
72:      }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts/contracts/access/Ownable.sol#L69-L72

File: lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol

74       function transferOwnership(address newOwner) public virtual onlyOwner {
75           require(newOwner != address(0), "Ownable: new owner is the zero address");
76           _transferOwnership(newOwner);
77:      }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol#L74-L77

[L‑04] Unsafe downcast

When a type is downcast to a smaller type, the higher order bits are truncated, effectively applying a modulo to the original value. Without any other checks, this wrapping will lead to unexpected behavior and bugs

There are 3 instances of this issue:

File: src/contracts/core/StrategyManager.sol

/// @audit uint32
405:                  withdrawalStartBlock: uint32(block.number),

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L405

File: src/contracts/pods/DelayedWithdrawalRouter.sol

/// @audit uint32
64:                   blockCreated: uint32(block.number)

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L64

File: src/contracts/pods/EigenPod.sol

/// @audit uint32
455:          mostRecentWithdrawalBlockNumber = uint32(block.number);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L455

[L‑05] Loss of precision

Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator

There are 4 instances of this issue:

File: src/contracts/strategies/StrategyBase.sol

99:                   newShares = (amount * totalShares) / priorTokenBalance;

152:              amountToSend = (_tokenBalance() * amountShares) / priorTotalShares;

176:              return (_tokenBalance() * amountShares) / totalShares;

201:              return (amountUnderlying * totalShares) / tokenBalance;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L99

[L‑06] Upgradeable contract not initialized

Upgradeable contracts are initialized via an initializer function rather than by a constructor. Leaving such a contract uninitialized may lead to it being taken over by a malicious user

There are 6 instances of this issue:

File: src/contracts/core/StrategyManager.sol

/// @audit missing __Ownable_init()
26:   contract StrategyManager is

/// @audit missing __ReentrancyGuard_init()
26:   contract StrategyManager is

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L26

File: src/contracts/pods/DelayedWithdrawalRouter.sol

/// @audit missing __Ownable_init()
11:   contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable, Pausable, IDelayedWithdrawalRouter {

/// @audit missing __ReentrancyGuard_init()
11:   contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable, Pausable, IDelayedWithdrawalRouter {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L11

File: src/contracts/pods/EigenPodManager.sol

/// @audit missing __Ownable_init()
31:   contract EigenPodManager is Initializable, OwnableUpgradeable, Pausable, IEigenPodManager, EigenPodPausingConstants {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L31

File: src/contracts/pods/EigenPod.sol

/// @audit missing __ReentrancyGuard_init()
34:   contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L34

[L‑07] Array lengths not checked

If the length of the arrays are not required to be of the same length, user operations may not be fully executed

There are 3 instances of this issue:

File: src/contracts/core/StrategyManager.sol

456       function completeQueuedWithdrawals(
457           QueuedWithdrawal[] calldata queuedWithdrawals,
458           IERC20[][] calldata tokens,
459           uint256[] calldata middlewareTimesIndexes,
460           bool[] calldata receiveAsTokens
461       ) external
462           onlyWhenNotPaused(PAUSED_WITHDRAWALS)
463           // check that the address that the staker *was delegated to* – at the time that they queued the withdrawal – is not frozen
464:          nonReentrant

482       function slashShares(
483           address slashedAddress,
484           address recipient,
485           IStrategy[] calldata strategies,
486           IERC20[] calldata tokens,
487           uint256[] calldata strategyIndexes,
488           uint256[] calldata shareAmounts
489       )
490           external
491           onlyOwner
492           onlyFrozen(slashedAddress)
493:          nonReentrant

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L456-L464

File: src/contracts/pods/EigenPod.sol

305       function verifyAndProcessWithdrawal(
306           BeaconChainProofs.WithdrawalProofs calldata withdrawalProofs, 
307           bytes calldata validatorFieldsProof,
308           bytes32[] calldata validatorFields,
309           bytes32[] calldata withdrawalFields,
310           uint256 beaconChainETHStrategyIndex,
311           uint64 oracleBlockNumber
312       )
313           external
314           onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_WITHDRAWAL)
315           onlyNotFrozen
316           /** 
317            * Check that the provided block number being proven against is after the `mostRecentWithdrawalBlockNumber`.
318            * Without this check, there is an edge case where a user proves a past withdrawal for a validator whose funds they already withdrew,
319            * as a way to "withdraw the same funds twice" without providing adequate proof.
320            * Note that this check is not made using the oracleBlockNumber as in the `verifyWithdrawalCredentials` proof; instead this proof
321            * proof is made for the block number of the withdrawal, which may be within 8192 slots of the oracleBlockNumber. 
322            * This difference in modifier usage is OK, since it is still not possible to `verifyAndProcessWithdrawal` against a slot that occurred
323            * *prior* to the proof provided in the `verifyWithdrawalCredentials` function.
324            */
325:          proofIsForValidBlockNumber(Endian.fromLittleEndianUint64(withdrawalProofs.blockNumberRoot))

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L305-L325

[L‑08] Missing checks for address(0x0) when assigning values to address state variables

There is one instance of this issue:

File: src/contracts/core/StrategyManager.sol

848:          strategyWhitelister = newStrategyWhitelister;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L848

[L‑09] abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256()

Use abi.encode() instead which will pad items to 32 bytes, which will prevent hash collisions (e.g. abi.encodePacked(0x123,0x456) => 0x123456 => abi.encodePacked(0x1,0x23456), but abi.encode(0x123,0x456) => 0x0...1230...456). "Unless there is a compelling reason, abi.encode should be preferred". If there is only one argument to abi.encodePacked() it can often be cast to bytes() or bytes32() instead.

If all arguments are strings and or bytes, bytes.concat() should be used instead

There are 2 instances of this issue:

File: src/contracts/core/StrategyManager.sol

277:              digestHash = keccak256(abi.encodePacked("\x19\x01", domain_separator, structHash));

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L277

File: src/contracts/pods/EigenPodManager.sol

200                       keccak256(abi.encodePacked(
201                           beaconProxyBytecode, 
202                           abi.encode(eigenPodBeacon, "")
203:                      )) //bytecode

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L200-L203

[L‑10] Use Ownable2Step rather than Ownable

Ownable2Step and Ownable2StepUpgradeable prevent the contract ownership from mistakenly being transferred to an address that cannot handle it (e.g. due to a typo in the address), by requiring that the recipient of the owner permissions actively accept via a contract call of its own.

There are 3 instances of this issue:

File: src/contracts/core/StrategyManager.sol

26    contract StrategyManager is
27        Initializable,
28        OwnableUpgradeable,
29        ReentrancyGuardUpgradeable,
30        Pausable,
31:       StrategyManagerStorage

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L26-L31

File: src/contracts/pods/DelayedWithdrawalRouter.sol

11    contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable, Pausable, IDelayedWithdrawalRouter {
...
178:  }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L11-L178

File: src/contracts/pods/EigenPodManager.sol

31    contract EigenPodManager is Initializable, OwnableUpgradeable, Pausable, IEigenPodManager, EigenPodPausingConstants {
...
227:  }

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L31-L227

Non-critical Issues

[N‑01] Constants in comparisons should appear on the left side

Doing so will prevent typo bugs

There are 118 instances of this issue:

File: src/contracts/middleware/BLSPublicKeyCompendium.sol

49:          require(shouldBeZero.X == 0 && shouldBeZero.Y == 0, "BLSPublicKeyCompendium.registerBLSPublicKey: incorrect schnorr singature");

49:          require(shouldBeZero.X == 0 && shouldBeZero.Y == 0, "BLSPublicKeyCompendium.registerBLSPublicKey: incorrect schnorr singature");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/BLSPublicKeyCompendium.sol#L49-L49

File: src/contracts/middleware/BLSSignatureChecker.sol

204:         if (placeholder != 0) {

348:         if (placeholder != 0) {

468:         require(input[0] == 1, "BLSSignatureChecker.checkSignatures: Pairing unsuccessful");

503:             opStake.nextUpdateBlockNumber == 0 || opStake.nextUpdateBlockNumber > referenceBlockNumber,

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/BLSSignatureChecker.sol#L204-L204

File: src/contracts/libraries/BN254.sol

95:          if (p.X == 0 && p.Y == 0) {

204:         return out[0] != 0;

251:         return (success, out[0] != 0);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BN254.sol#L95-L95

File: lib/openzeppelin-contracts/contracts/utils/Create2.sol

37:          require(bytecode.length != 0, "Create2: bytecode length is zero");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts/contracts/utils/Create2.sol#L37-L37

File: src/contracts/pods/DelayedWithdrawalRouter.sol

61:          if (withdrawalAmount != 0) {

159:         if (amountToSend != 0) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L61-L61

File: lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol

61:          if (signature.length == 65) {

74:          } else if (signature.length == 64) {

166:         if (v != 27 && v != 28) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol#L61-L61

File: src/contracts/pods/EigenPod.sol

147:         require(_REQUIRED_BALANCE_WEI % GWEI_TO_WEI == 0, "EigenPod.contructor: _REQUIRED_BALANCE_WEI is not a whole number of gwei");

160:         require(msg.value == 32 ether, "EigenPod.stake: must initially stake for any validator with 32 ether");

268:         if (validatorCurrentBalanceGwei == 0) {

270:             require(slashedStatus == 1, "EigenPod.verifyOvercommittedStake: Validator must be slashed to be overcommitted");

417:         if (amountToSend != 0) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L147-L147

File: lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol

84:          if (valueIndex != 0) {

118:         return set._indexes[value] != 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol#L84-L84

File: src/contracts/middleware/example/HashThreshold.sol

65:          require(certifiedMessageMetadatas[message].validAfterBlock == 0, "Message already certified");

109:         require(decaHash(message) >> (256 - numZeroes) == 0, "Message does not hash to enough zeroes");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/example/HashThreshold.sol#L65-L65

File: lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol

81:              (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol#L81-L81

File: script/M1_Deploy.s.sol

153:         if (chainId == 1) {

410:             require(deployedStrategyArray[i].paused() == 0, "StrategyBase: init paused status set incorrectly");

423:         require(strategyManager.paused() == 0, "strategyManager: init paused status set incorrectly");

426:         require(eigenPodManager.paused() == 30, "eigenPodManager: init paused status set incorrectly");

427:         require(delayedWithdrawalRouter.paused() == 0, "delayedWithdrawalRouter: init paused status set incorrectly");

439:         require(eigenPodImplementation.REQUIRED_BALANCE_WEI() == 31 ether,

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/script/M1_Deploy.s.sol#L153-L153

File: lib/openzeppelin-contracts/contracts/utils/math/Math.sol

47:          return a == 0 ? 0 : (a - 1) / b + 1;

73:              if (prod1 == 0) {

159:         if (a == 0) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts/contracts/utils/math/Math.sol#L47-L47

File: src/contracts/libraries/Merkle.sol

51:              if(index % 2 == 0) {

102:             if(index % 2 == 0) {

143:         while (numNodesInLayer != 0) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/Merkle.sol#L51-L51

File: src/contracts/operators/MerkleDelegationTerms.sol

113:             if (amountToSend != 0) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/operators/MerkleDelegationTerms.sol#L113-L113

File: src/contracts/permissions/Pausable.sol

44:          require(_paused == 0, "Pausable: contract is paused");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L44-L44

File: src/contracts/middleware/PaymentManager.sol

203:         if (operatorToPayment[msg.sender].fromTaskNumber == 0) {

410:         if (diff == 1) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/PaymentManager.sol#L203-L203

File: src/contracts/middleware/RegistryBase.sol

73:          require(_NUMBER_OF_QUORUMS <= 2 && _NUMBER_OF_QUORUMS > 0, "RegistryBase: NUMBER_OF_QUORUMS must be less than or equal to 2 and greater than 0");

118:             index == 0 || pubkeyHashToIndexHistory[pubkeyHash][index - 1].toBlockNumber <= blockNumber,

127:             operatorIndex.toBlockNumber == 0 || blockNumber < operatorIndex.toBlockNumber,

144:             index == 0 || totalOperatorsHistory[index - 1].toBlockNumber <= blockNumber,

154:             operatorIndex.toBlockNumber == 0 || blockNumber < operatorIndex.toBlockNumber,

216:             (operatorStake.nextUpdateBlockNumber == 0 || operatorStake.nextUpdateBlockNumber > blockNumber)

220:             (operatorStake.firstQuorumStake != 0 || operatorStake.secondQuorumStake != 0) 

249:         if (pubkeyHashToStakeHistory[pubkeyHash].length == 0) {

259:             (operatorStake.nextUpdateBlockNumber == 0 || operatorStake.nextUpdateBlockNumber > blockNumber)

263:             (operatorStake.firstQuorumStake == 0 && operatorStake.secondQuorumStake == 0) 

275:         if (historyLength == 0) {

531:         if (pubkeyHashToStakeHistory[pubkeyHash].length != 0) {

590:         if ((operatorType & 1) == 1) {

599:         if ((operatorType & 2) == 2) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/RegistryBase.sol#L73-L73

File: lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol

55:              (value == 0) || (token.allowance(address(this), spender) == 0),

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol#L55-L55

File: src/contracts/core/Slasher.sol

179:         if (_operatorToWhitelistedContractsByUpdate[operator].sizeOf() != 1) {

181:             require(_operatorToWhitelistedContractsByUpdate[operator].remove(_addressToUint(msg.sender)) != 0, 

204:         require(_operatorToWhitelistedContractsByUpdate[operator].remove(_addressToUint(msg.sender)) != 0,

272:         if (_operatorToMiddlewareTimes[operator].length == 0) {

284:         if (withdrawalStartBlock >= update.stalestUpdateBlock && _operatorToWhitelistedContractsByUpdate[operator].size == 0) {

421:         if (_operatorToMiddlewareTimesLength != 0) {

434:         if (_operatorToWhitelistedContractsByUpdate[operator].size == 0) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/Slasher.sol#L179-L179

File: lib/forge-std/src/StdAssertions.sol

204:         if (b == 0) return assertEq(a, b); // If the left is 0, right must be too.

224:         if (b == 0) return assertEq(a, b, err); // If the left is 0, right must be too.

240:         if (b == 0) return assertEq(a, b); // If the left is 0, right must be too.

261:         if (b == 0) return assertEq(a, b, err); // If the left is 0, right must be too.

272:         if (b == 0) return assertEq(a, b); // If the left is 0, right must be too.

287:         if (b == 0) return assertEq(a, b, err); // If the left is 0, right must be too.

298:         if (b == 0) return assertEq(a, b); // If the left is 0, right must be too.

316:         if (b == 0) return assertEq(a, b, err); // If the left is 0, right must be too.

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/forge-std/src/StdAssertions.sol#L204-L204

File: lib/forge-std/src/StdChains.sol

74:          require(bytes(chainAlias).length != 0, "StdChains getChain(string): Chain alias cannot be the empty string.");

79:              chain.chainId != 0,

87:          require(chainId != 0, "StdChains getChain(uint256): Chain ID cannot be 0.");

94:              chain.chainId != 0,

104:             bytes(chainAlias).length != 0,

108:         require(chain.chainId != 0, "StdChains setChain(string,ChainData): Chain ID cannot be 0.");

114:             bytes(foundAlias).length == 0 || keccak256(bytes(foundAlias)) == keccak256(bytes(chainAlias)),

144:             if (b >= 0x61 && b <= 0x7A) {

156:         if (bytes(chain.rpcUrl).length == 0) {

169:                 if (keccak256(notFoundError) != keccak256(err) || bytes(chain.rpcUrl).length == 0) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/forge-std/src/StdChains.sol#L74-L74

File: lib/forge-std/src/StdCheats.sol

213:         if (chainId == 10 || chainId == 420) {

216:         } else if (chainId == 42161 || chainId == 421613) {

219:         } else if (chainId == 43114 || chainId == 43113) {

434:         require(b.length <= 32, "StdCheats _bytesToUint(bytes): Bytes length exceeds 32.");

606:                 totSupData.length != 0,

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/forge-std/src/StdCheats.sol#L213-L213

File: lib/forge-std/src/StdStorage.sol

51:          if (reads.length == 1) {

154:         if (v == 0) return false;

155:         if (v == 1) return true;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/forge-std/src/StdStorage.sol#L51-L51

File: lib/forge-std/src/StdUtils.sol

40:          if (x <= 3 && size > x) return min + x;

52:              if (rem == 0) return min;

47:              if (rem == 0) return max;

84:          require(b.length <= 32, "StdUtils bytesToUint(bytes): Bytes length exceeds 32.");

94:          if (nonce == 0x00)      return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, bytes1(0x80))));

95:          if (nonce <= 0x7f)      return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, uint8(nonce))));

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/forge-std/src/StdUtils.sol#L40-L40

File: src/contracts/strategies/StrategyBase.sol

92:          if (totalShares == 0) {

96:              if (priorTokenBalance == 0) {

104:         require(newShares != 0, "StrategyBase.deposit: newShares cannot be zero");

139:         require(updatedTotalShares >= MIN_NONZERO_TOTAL_SHARES || updatedTotalShares == 0,

173:         if (totalShares == 0) {

198:         if (tokenBalance == 0 || totalShares == 0) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L92-L92

File: src/contracts/core/StrategyManager.sol

196:         if (amount != 0) {

362:                 require(strategies.length == 1,

364:                 require(shares[i] % GWEI_TO_WEI == 0,

422:         if (undelegateIfPossible && stakerStrategyList[msg.sender].length == 0) {

632:         require(shares != 0, "StrategyManager._addShares: shares should not be zero!");

635:         if (stakerStrategyShares[depositor][strategy] == 0) {

685:         require(shareAmount != 0, "StrategyManager._removeShares: shareAmount should not be zero!");

700:         if (userShares == 0) {

812:         require(stakerStrategyList[depositor].length == 0, "StrategyManager._undelegate: depositor has active deposits");

823:         if (amountToDecrement != 0) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L196-L196

File: lib/openzeppelin-contracts/contracts/utils/Strings.sol

20:          if (value == 0) {

25:          while (temp != 0) {

30:          while (value != 0) {

42:          if (value == 0) {

47:          while (temp != 0) {

65:          require(value == 0, "Strings: hex length insufficient");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts/contracts/utils/Strings.sol#L20-L20

File: src/contracts/middleware/VoteWeigherBaseStorage.sol

67:          require(_NUMBER_OF_QUORUMS != 0, "VoteWeigherBaseStorage.constructor: _NUMBER_OF_QUORUMS == 0");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/VoteWeigherBaseStorage.sol#L67-L67

[N‑02] Variables need not be initialized to zero

The default value for variables is zero, so initializing them to zero is superfluous.

There are 15 instances of this issue:

File: lib/openzeppelin-contracts/contracts/access/AccessControl.sol

57:      bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts/contracts/access/AccessControl.sol#L57-L57

File: src/contracts/libraries/BeaconChainProofs.sol

65:      uint256 internal constant SLOT_INDEX = 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BeaconChainProofs.sol#L65-L65

File: src/contracts/pods/DelayedWithdrawalRouter.sol

16:      uint8 internal constant PAUSED_DELAYED_WITHDRAWAL_CLAIMS = 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L16-L16

File: src/contracts/core/DelegationManager.sol

25:      uint8 internal constant PAUSED_NEW_DELEGATION = 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/DelegationManager.sol#L25-L25

File: src/contracts/pods/EigenPodPausingConstants.sol

10:      uint8 internal constant PAUSED_NEW_EIGENPODS = 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodPausingConstants.sol#L10-L10

File: src/contracts/middleware/example/HashThreshold.sol

26:      uint32 public taskNumber = 0;

27:      uint32 public latestServeUntilBlock = 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/example/HashThreshold.sol#L26-L26

File: src/contracts/permissions/Pausable.sol

22:      uint256 constant internal UNPAUSE_ALL = 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L22-L22

File: src/contracts/middleware/PaymentManager.sol

24:      uint8 constant internal PAUSED_NEW_PAYMENT_COMMIT = 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/PaymentManager.sol#L24-L24

File: src/contracts/core/Slasher.sol

24:      uint256 private constant HEAD = 0;

26:      uint8 internal constant PAUSED_OPT_INTO_SLASHING = 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/Slasher.sol#L24-L24

File: src/contracts/strategies/StrategyBase.sol

22:      uint8 internal constant PAUSED_DEPOSITS = 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L22-L22

File: src/contracts/core/StrategyManager.sol

38:      uint8 internal constant PAUSED_DEPOSITS = 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L38-L38

File: src/contracts/libraries/StructuredLinkedList.sol

12:      uint256 private constant _NULL = 0;

13:      uint256 private constant _HEAD = 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/StructuredLinkedList.sol#L12-L12

[N‑03] Imports could be organized more systematically

The contract's interface should be imported first, followed by each of the interfaces it uses, followed by all other files. The examples below do not follow this layout.

There are 13 instances of this issue:

File: src/contracts/middleware/BLSRegistry.sol

4:   import "./RegistryBase.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/BLSRegistry.sol#L4-L4

File: src/contracts/pods/BeaconChainOracle.sol

4:   import "@openzeppelin/contracts/access/Ownable.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/BeaconChainOracle.sol#L4-L4

File: src/contracts/pods/DelayedWithdrawalRouter.sol

6:   import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L6-L6

File: src/contracts/pods/EigenPod.sol

11:  import "../libraries/Endian.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L11-L11

File: src/contracts/pods/EigenPodManager.sol

5:   import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";

9:   import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L5-L5

File: src/contracts/interfaces/IEigenPod.sol

4:   import "../libraries/BeaconChainProofs.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IEigenPod.sol#L4-L4

File: script/M1_Deploy.s.sol

7:   import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/script/M1_Deploy.s.sol#L7-L7

File: lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol

6:   import "../utils/ContextUpgradeable.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol#L6-L6

File: src/contracts/middleware/PaymentManager.sol

5:   import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/PaymentManager.sol#L5-L5

File: src/contracts/middleware/RegistryBase.sol

4:   import "@openzeppelin/contracts/utils/math/Math.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/RegistryBase.sol#L4-L4

File: src/contracts/core/Slasher.sol

9:   import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/Slasher.sol#L9-L9

File: src/contracts/strategies/StrategyBase.sol

7:   import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L7-L7

[N‑04] Large numeric literals should use underscores for readability

There are 14 instances of this issue:

File: script/Allocate.s.sol

47:      uint256 wethAmount = 100000000000000000000;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/script/Allocate.s.sol#L47-L47

File: src/contracts/pods/EigenPod.sol

41:      uint256 internal constant VERIFY_OVERCOMMITTED_WINDOW_BLOCKS = 50400;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L41-L41

File: src/contracts/middleware/example/HashThreshold.sol

78:          require(stakeSigned >= 666667 * uint256(totalStake) / 1000000, "Need more than 2/3 of stake to sign");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/middleware/example/HashThreshold.sol#L78-L78

File: lib/forge-std/src/StdChains.sol

190:         setChainWithDefaultRpcUrl("anvil", ChainData("Anvil", 31337, "http://127.0.0.1:8545"));

198:             "sepolia", ChainData("Sepolia", 11155111, "https://sepolia.infura.io/v3/f4a0bdad42674adab5fc0ac077ffab2b")

202:         setChainWithDefaultRpcUrl("arbitrum_one", ChainData("Arbitrum One", 42161, "https://arb1.arbitrum.io/rpc"));

204:             "arbitrum_one_goerli", ChainData("Arbitrum One Goerli", 421613, "https://goerli-rollup.arbitrum.io/rpc")

206:         setChainWithDefaultRpcUrl("arbitrum_nova", ChainData("Arbitrum Nova", 42170, "https://nova.arbitrum.io/rpc"));

209:             "polygon_mumbai", ChainData("Polygon Mumbai", 80001, "https://rpc-mumbai.maticvigil.com")

211:         setChainWithDefaultRpcUrl("avalanche", ChainData("Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc"));

213:             "avalanche_fuji", ChainData("Avalanche Fuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc")

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/forge-std/src/StdChains.sol#L190-L190

File: lib/forge-std/src/StdCheats.sol

216:         } else if (chainId == 42161 || chainId == 421613) {

219:         } else if (chainId == 43114 || chainId == 43113) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/forge-std/src/StdCheats.sol#L216-L216

File: lib/forge-std/src/StdMath.sol

10:              return 57896044618658097711785492504343953926634992332820282019728792003956564819968;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/lib/forge-std/src/StdMath.sol#L10-L10

[N‑05] Upgradeable contract is missing a __gap[50] storage variable to allow for new storage variables in later versions

See this link for a description of this storage variable. While some contracts may not currently be sub-classed, adding the variable now protects against forgetting to add it in the future.

There is one instance of this issue:

File: src/contracts/core/StrategyManager.sol

26    contract StrategyManager is
27        Initializable,
28        OwnableUpgradeable,
29        ReentrancyGuardUpgradeable,
30        Pausable,
31:       StrategyManagerStorage

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L26-L31

[N‑06] Import declarations should import specific identifiers, rather than the whole file

Using import declarations of the form import {<identifier_name>} from "some/file.sol" avoids polluting the symbol namespace making flattened files smaller, and speeds up compilation

There are 71 instances of this issue:

File: src/contracts/core/StrategyManager.sol

4:    import "@openzeppelin/contracts/interfaces/IERC1271.sol";

5:    import "@openzeppelin/contracts/utils/Address.sol";

6:    import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";

7:    import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";

8:    import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";

9:    import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

10:   import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

11:   import "../interfaces/IEigenPodManager.sol";

12:   import "../permissions/Pausable.sol";

13:   import "./StrategyManagerStorage.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L4

File: src/contracts/core/StrategyManagerStorage.sol

4:    import "../interfaces/IStrategyManager.sol";

5:    import "../interfaces/IStrategy.sol";

6:    import "../interfaces/IEigenPodManager.sol";

7:    import "../interfaces/IDelegationManager.sol";

8:    import "../interfaces/ISlasher.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManagerStorage.sol#L4

File: src/contracts/interfaces/IDelegationManager.sol

4:    import "./IDelegationTerms.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IDelegationManager.sol#L4

File: src/contracts/interfaces/IEigenPodManager.sol

4:    import "./IStrategyManager.sol";

5:    import "./IEigenPod.sol";

6:    import "./IBeaconChainOracle.sol";

7:    import "./IPausable.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IEigenPodManager.sol#L4

File: src/contracts/interfaces/IEigenPod.sol

4:    import "../libraries/BeaconChainProofs.sol";

5:    import "./IEigenPodManager.sol";

6:    import "./IBeaconChainOracle.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IEigenPod.sol#L4

File: src/contracts/interfaces/IPausable.sol

4:    import "../interfaces/IPauserRegistry.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IPausable.sol#L4

File: src/contracts/interfaces/IServiceManager.sol

4:    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

5:    import "./IDelegationManager.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IServiceManager.sol#L4

File: src/contracts/interfaces/IStrategyManager.sol

4:    import "./IStrategy.sol";

5:    import "./ISlasher.sol";

6:    import "./IDelegationManager.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IStrategyManager.sol#L4

File: src/contracts/interfaces/IStrategy.sol

4:    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IStrategy.sol#L4

File: src/contracts/libraries/BeaconChainProofs.sol

5:    import "./Merkle.sol";

6:    import "../libraries/Endian.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BeaconChainProofs.sol#L5

File: src/contracts/permissions/Pausable.sol

5:    import "../interfaces/IPausable.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L5

File: src/contracts/permissions/PauserRegistry.sol

4:    import "../interfaces/IPauserRegistry.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/PauserRegistry.sol#L4

File: src/contracts/pods/DelayedWithdrawalRouter.sol

4:    import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";

5:    import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";

6:    import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";

7:    import "../interfaces/IEigenPodManager.sol";

8:    import "../interfaces/IDelayedWithdrawalRouter.sol";

9:    import "../permissions/Pausable.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L4

File: src/contracts/pods/EigenPodManager.sol

4:    import "@openzeppelin/contracts/utils/Create2.sol";

5:    import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";

6:    import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";

8:    import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";

9:    import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";

11:   import "../interfaces/IStrategyManager.sol";

12:   import "../interfaces/IDelegationManager.sol";

13:   import "../interfaces/IEigenPodManager.sol";

14:   import "../interfaces/IETHPOSDeposit.sol";

15:   import "../interfaces/IEigenPod.sol";

17:   import "../interfaces/IBeaconChainOracle.sol";

19:   import "../permissions/Pausable.sol";

20:   import "./EigenPodPausingConstants.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L4

File: src/contracts/pods/EigenPod.sol

4:    import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";

5:    import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";

6:    import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";

7:    import "@openzeppelin-upgrades/contracts/utils/AddressUpgradeable.sol";

9:    import "../libraries/BeaconChainProofs.sol";

10:   import "../libraries/BytesLib.sol";

11:   import "../libraries/Endian.sol";

13:   import "../interfaces/IETHPOSDeposit.sol";

14:   import "../interfaces/IEigenPodManager.sol";

15:   import "../interfaces/IEigenPod.sol";

16:   import "../interfaces/IDelayedWithdrawalRouter.sol";

17:   import "../interfaces/IPausable.sol";

19:   import "./EigenPodPausingConstants.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L4

File: src/contracts/strategies/StrategyBase.sol

4:    import "../interfaces/IStrategyManager.sol";

5:    import "../permissions/Pausable.sol";

6:    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

7:    import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

8:    import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L4

[N‑07] Missing initializer modifier on constructor

OpenZeppelin recommends that the initializer modifier be applied to constructors in order to avoid potential griefs, social engineering, or exploits. Ensure that the modifier is applied to the implementation contract. If the default constructor is currently being used, it should be changed to be an explicit one with the modifier applied.

There is one instance of this issue:

File: src/contracts/pods/DelayedWithdrawalRouter.sol

44:       constructor(IEigenPodManager _eigenPodManager) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L44

[N‑08] Unused file

The file is never imported by any other source file. If the file is needed for tests, it should be moved to a test directory

There is one instance of this issue:

File: src/contracts/interfaces/IServiceManager.sol

1:    // SPDX-License-Identifier: BUSL-1.1

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IServiceManager.sol#L1

[N‑09] The nonReentrant modifier should occur before all other modifiers

This is a best-practice to protect against reentrancy in other modifiers

There are 9 instances of this issue:

File: src/contracts/core/StrategyManager.sol

169:          nonReentrant

185:          nonReentrant

224:          nonReentrant

259:          nonReentrant

339:          nonReentrant

446:          nonReentrant

464:          nonReentrant

493:          nonReentrant

540:          nonReentrant

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L169

[N‑10] Adding a return statement when the function defines a named return variable, is redundant

There are 2 instances of this issue:

File: src/contracts/core/StrategyManager.sol

671:          return shares;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L671

File: src/contracts/strategies/StrategyBase.sol

111:          return newShares;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L111

[N‑11] public functions not called by the contract should be declared external instead

Contracts are allowed to override their parents' functions and change the visibility from external to public.

There is one instance of this issue:

File: src/contracts/pods/EigenPodManager.sol

193:      function getPod(address podOwner) public view returns (IEigenPod) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L193

[N‑12] constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

There are 56 instances of this issue:

File: src/contracts/libraries/BeaconChainProofs.sol

/// @audit 4
179:          uint256 bitShiftAmount = (validatorIndex % 4) * 64;

/// @audit 64
179:          uint256 bitShiftAmount = (validatorIndex % 4) * 64;

/// @audit 32
205:          require(proof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorFields: Proof has incorrect length");

/// @audit 32
227:          require(proof.length == 32 * ((BALANCE_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorBalance: Proof has incorrect length");

/// @audit 4
233:          uint256 balanceIndex = uint256(validatorIndex/4);

/// @audit 32
256:          require(proofs.blockHeaderProof.length == 32 * (BEACON_STATE_FIELD_TREE_HEIGHT + BLOCK_ROOTS_TREE_HEIGHT),

/// @audit 32
258:          require(proofs.withdrawalProof.length == 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT + WITHDRAWALS_TREE_HEIGHT + 1),

/// @audit 32
260:          require(proofs.executionPayloadProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT),

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BeaconChainProofs.sol#L179

File: src/contracts/libraries/Endian.sol

/// @audit 192
9:            n = uint64(uint256(lenum >> 192));

/// @audit 56
11:               (n >> 56) |

/// @audit 0x00FF000000000000
12:               ((0x00FF000000000000 & n) >> 40) |

/// @audit 40
12:               ((0x00FF000000000000 & n) >> 40) |

/// @audit 0x0000FF0000000000
13:               ((0x0000FF0000000000 & n) >> 24) |

/// @audit 24
13:               ((0x0000FF0000000000 & n) >> 24) |

/// @audit 0x000000FF00000000
14:               ((0x000000FF00000000 & n) >> 8) |

/// @audit 8
14:               ((0x000000FF00000000 & n) >> 8) |

/// @audit 0x00000000FF000000
15:               ((0x00000000FF000000 & n) << 8) |

/// @audit 8
15:               ((0x00000000FF000000 & n) << 8) |

/// @audit 0x0000000000FF0000
16:               ((0x0000000000FF0000 & n) << 24) |

/// @audit 24
16:               ((0x0000000000FF0000 & n) << 24) |

/// @audit 0x000000000000FF00
17:               ((0x000000000000FF00 & n) << 40) |

/// @audit 40
17:               ((0x000000000000FF00 & n) << 40) |

/// @audit 0x00000000000000FF
18:               ((0x00000000000000FF & n) << 56);

/// @audit 56
18:               ((0x00000000000000FF & n) << 56);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/Endian.sol#L9

File: src/contracts/libraries/Merkle.sol

/// @audit 32
50:           for (uint256 i = 32; i <= proof.length; i+=32) {

/// @audit 0x00
54:                       mstore(0x00, computedHash)

/// @audit 0x20
55:                       mstore(0x20, mload(add(proof, i)))

/// @audit 0x00
56:                       computedHash := keccak256(0x00, 0x40)

/// @audit 0x40
56:                       computedHash := keccak256(0x00, 0x40)

/// @audit 2
57:                       index := div(index, 2)

/// @audit 0x00
62:                       mstore(0x00, mload(add(proof, i)))

/// @audit 0x20
63:                       mstore(0x20, computedHash)

/// @audit 0x00
64:                       computedHash := keccak256(0x00, 0x40)

/// @audit 0x40
64:                       computedHash := keccak256(0x00, 0x40)

/// @audit 2
65:                       index := div(index, 2)

/// @audit 32
101:          for (uint256 i = 32; i <= proof.length; i+=32) {

/// @audit 0x00
105:                      mstore(0x00, mload(computedHash))

/// @audit 0x20
106:                      mstore(0x20, mload(add(proof, i)))

/// @audit 2000
107:                      if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}

/// @audit 2
107:                      if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}

/// @audit 0x00
107:                      if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}

/// @audit 0x40
107:                      if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}

/// @audit 0x20
107:                      if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}

/// @audit 0
107:                      if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}

/// @audit 2
108:                      index := div(index, 2)

/// @audit 0x00
113:                      mstore(0x00, mload(add(proof, i)))

/// @audit 0x20
114:                      mstore(0x20, mload(computedHash))

/// @audit 2000
115:                      if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}

/// @audit 2
115:                      if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}

/// @audit 0x00
115:                      if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}

/// @audit 0x40
115:                      if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}

/// @audit 0x20
115:                      if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}

/// @audit 0
115:                      if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}

/// @audit 2
116:                      index := div(index, 2)

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/Merkle.sol#L50

File: src/contracts/pods/EigenPod.sol

/// @audit 32
160:          require(msg.value == 32 ether, "EigenPod.stake: must initially stake for any validator with 32 ether");

/// @audit 32
161:          ethPOS.deposit{value : 32 ether}(pubkey, _podWithdrawalCredentials(), signature, depositDataRoot);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L160

[N‑13] Cast is more restrictive than the type of the variable being assigned

If address foo is being used in an expression such as IERC20 token = FooToken(foo), then the more specific cast to FooToken is a waste because the only thing the compiler will check for is that FooToken extends IERC20 - it won't check any of the function signatures. Therefore, it makes more sense to do IERC20 token = IERC20(token) or better yet FooToken token = FooToken(foo). The former may allow the file in which it's used to remove the import for FooToken

There is one instance of this issue:

File: src/contracts/pods/EigenPod.sol

/// @audit uint32 vs uint64
455:          mostRecentWithdrawalBlockNumber = uint32(block.number);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L455

[N‑14] Numeric values having to do with time should use time units for readability

There are units for seconds, minutes, hours, days, and weeks, and since they're defined, they should be used

There is one instance of this issue:

File: src/contracts/core/StrategyManagerStorage.sol

/// @audit 50400
46:       uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 50400;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManagerStorage.sol#L46

[N‑15] Use a more recent version of solidity

Use a solidity version of at least 0.8.13 to get the ability to use using for with a list of free functions

There are 3 instances of this issue:

File: src/contracts/core/StrategyManager.sol

2:    pragma solidity =0.8.12;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L2

File: src/contracts/pods/EigenPod.sol

2:    pragma solidity =0.8.12;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L2

File: src/contracts/strategies/StrategyBase.sol

2:    pragma solidity =0.8.12;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L2

[N‑16] Expressions for constant values such as a call to keccak256(), should use immutable rather than constant

While it doesn't save any gas because the compiler knows that developers often make this mistake, it's still best to use the right tool for the task at hand. There is a difference between constant variables and immutable variables, and they should each be used in their appropriate contexts. constants should be used for literal values written into the code, and immutable variables should be used for expressions, or values calculated in, or passed into the constructor.

There are 2 instances of this issue:

File: src/contracts/core/StrategyManagerStorage.sol

17        bytes32 public constant DOMAIN_TYPEHASH =
18:           keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");

20        bytes32 public constant DEPOSIT_TYPEHASH =
21:           keccak256("Deposit(address strategy,address token,uint256 amount,uint256 nonce,uint256 expiry)");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManagerStorage.sol#L17-L18

[N‑17] Constant redefined elsewhere

Consider defining in only one contract so that values cannot become out of sync when only one location is updated. A cheap way to store constants in a single location is to create an internal constant in a library. If the variable is a local cache of another contract's value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don't get out of sync.

There are 6 instances of this issue:

File: src/contracts/pods/DelayedWithdrawalRouter.sol

/// @audit seen in src/contracts/core/StrategyManagerStorage.sol 
24:       uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 50400;

/// @audit seen in src/contracts/core/StrategyManagerStorage.sol 
27:       IEigenPodManager public immutable eigenPodManager;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L24

File: src/contracts/pods/EigenPodManager.sol

/// @audit seen in src/contracts/core/StrategyManagerStorage.sol 
49:       ISlasher public immutable slasher;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L49

File: src/contracts/pods/EigenPod.sol

/// @audit seen in src/contracts/pods/EigenPodManager.sol 
44:       IETHPOSDeposit public immutable ethPOS;

/// @audit seen in src/contracts/pods/DelayedWithdrawalRouter.sol 
50:       IEigenPodManager public immutable eigenPodManager;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L44

File: src/contracts/strategies/StrategyBase.sol

/// @audit seen in src/contracts/pods/EigenPodManager.sol 
31:       IStrategyManager public immutable strategyManager;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L31

[N‑18] Inconsistent spacing in comments

Some lines use // x and some use //x. The instances below point out the usages that don't follow the majority, within each file

There are 24 instances of this issue:

File: src/contracts/core/StrategyManager.sol

274:          //if chainid has changed, we must re-compute the domain separator

378:              //increment the loop

508:                   //withdraw the beaconChainETH to the recipient

568:                       //withdraw the beaconChainETH to the recipient

687:          //check that the user has sufficient shares

691:          //unchecked arithmetic since we just checked this above

722:              //loop through all of the strategies, find the right one, then replace

727:                      //replace the strategy with the last strategy in the list

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L274

File: src/contracts/interfaces/IEigenPod.sol

85:       ///@notice mapping that trackes proven partial withdrawals

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IEigenPod.sol#L85

File: src/contracts/libraries/BeaconChainProofs.sol

53:       //refer to the eigenlayer-cli proof library.  Despite being the same dimensions as the validator tree, the balance tree is merkleized differently

59:       //in beacon block body

87:       //in execution payload

94:       //In historicalBatch

97:       //Misc Constants

272:          //Next we verify the slot against the blockHeaderRoot

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BeaconChainProofs.sol#L53

File: src/contracts/libraries/Merkle.sol

52:                   // if ith bit of index is 0, then computedHash is a left sibling

60:                   // if ith bit of index is 1, then computedHash is a right sibling

103:                  // if ith bit of index is 0, then computedHash is a left sibling

111:                  // if ith bit of index is 1, then computedHash is a right sibling

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/Merkle.sol#L52

File: src/contracts/pods/EigenPodManager.sol

115:              //deploy a pod if the sender doesn't have one already

199:                      bytes32(uint256(uint160(podOwner))), //salt

203:                      )) //bytecode

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L115

File: src/contracts/pods/EigenPod.sol

271:              //Verify the validator fields, which contain the validator's slashed status

346:          //check if the withdrawal occured after mostRecentWithdrawalBlockNumber

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L271

[N‑19] Lines are too long

Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, the lines below should be split when they reach that length

There are 23 instances of this issue:

File: src/contracts/core/StrategyManager.sol

146:      function initialize(address initialOwner, address initialStrategyWhitelister, IPauserRegistry _pauserRegistry, uint256 initialPausedStatus, uint256 _withdrawalDelayBlocks)

533:       * so that, e.g., if the slashed QueuedWithdrawal contains a malicious strategy in the `strategies` array which always reverts on calls to its 'withdraw' function,

745:      function _completeQueuedWithdrawal(QueuedWithdrawal calldata queuedWithdrawal, IERC20[] calldata tokens, uint256 middlewareTimesIndex, bool receiveAsTokens) onlyNotFrozen(queuedWithdrawal.delegatedAddress) internal {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L146

File: src/contracts/interfaces/IBeaconChainOracle.sol

31:        * the state root must still receive one additional vote from an oracle signer to be confirmed. This behavior is intended, to minimize unexpected root confirmations.

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IBeaconChainOracle.sol#L31

File: src/contracts/interfaces/IDelayedWithdrawalRouter.sol

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

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IDelayedWithdrawalRouter.sol#L51

File: src/contracts/interfaces/IDelegationManager.sol

63:        * @notice Decreases the `staker`'s delegated shares in each entry of `strategies` by its respective `shares[i]`, typically called when the staker withdraws from EigenLayer

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IDelegationManager.sol#L63

File: src/contracts/interfaces/IEigenPod.sol

90:        * this contract. It also verifies the current (not effective) balance  of the validator.  It verifies the provided proof of the ETH validator against the beacon chain state

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IEigenPod.sol#L90

File: src/contracts/interfaces/IStrategyManager.sol

195:       * so that, e.g., if the slashed QueuedWithdrawal contains a malicious strategy in the `strategies` array which always reverts on calls to its 'withdraw' function,

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IStrategyManager.sol#L195

File: src/contracts/libraries/BeaconChainProofs.sol

205:          require(proof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorFields: Proof has incorrect length");

227:          require(proof.length == 32 * ((BALANCE_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorBalance: Proof has incorrect length");

273:          require(Merkle.verifyInclusionSha256(proofs.slotProof, proofs.blockHeaderRoot, proofs.slotRoot, SLOT_INDEX), "BeaconChainProofs.verifyWithdrawalProofs: Invalid slot merkle proof");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BeaconChainProofs.sol#L205

File: src/contracts/pods/DelayedWithdrawalRouter.sol

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

133:          return ((index >= _userWithdrawals[user].delayedWithdrawalsCompleted) && (block.number >= _userWithdrawals[user].delayedWithdrawals[index].blockCreated + withdrawalDelayBlocks));

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L131

File: src/contracts/pods/EigenPodManager.sol

37:       bytes internal constant beaconProxyBytecode = hex"608060405260405161090e38038061090e83398101604081905261002291610460565b61002e82826000610035565b505061058a565b61003e83610100565b6040516001600160a01b038416907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e90600090a260008251118061007f5750805b156100fb576100f9836001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e99190610520565b836102a360201b6100291760201c565b505b505050565b610113816102cf60201b6100551760201c565b6101725760405162461bcd60e51b815260206004820152602560248201527f455243313936373a206e657720626561636f6e206973206e6f74206120636f6e6044820152641d1c9858dd60da1b60648201526084015b60405180910390fd5b6101e6816001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d79190610520565b6102cf60201b6100551760201c565b61024b5760405162461bcd60e51b815260206004820152603060248201527f455243313936373a20626561636f6e20696d706c656d656e746174696f6e206960448201526f1cc81b9bdd08184818dbdb9d1c9858dd60821b6064820152608401610169565b806102827fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5060001b6102de60201b6100641760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60606102c883836040518060600160405280602781526020016108e7602791396102e1565b9392505050565b6001600160a01b03163b151590565b90565b6060600080856001600160a01b0316856040516102fe919061053b565b600060405180830381855af49150503d8060008114610339576040519150601f19603f3d011682016040523d82523d6000602084013e61033e565b606091505b5090925090506103508683838761035a565b9695505050505050565b606083156103c65782516103bf576001600160a01b0385163b6103bf5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610169565b50816103d0565b6103d083836103d8565b949350505050565b8151156103e85781518083602001fd5b8060405162461bcd60e51b81526004016101699190610557565b80516001600160a01b038116811461041957600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561044f578181015183820152602001610437565b838111156100f95750506000910152565b6000806040838503121561047357600080fd5b61047c83610402565b60208401519092506001600160401b038082111561049957600080fd5b818501915085601f8301126104ad57600080fd5b8151818111156104bf576104bf61041e565b604051601f8201601f19908116603f011681019083821181831017156104e7576104e761041e565b8160405282815288602084870101111561050057600080fd5b610511836020830160208801610434565b80955050505050509250929050565b60006020828403121561053257600080fd5b6102c882610402565b6000825161054d818460208701610434565b9190910192915050565b6020815260008251806020840152610576816040850160208701610434565b601f01601f19169190910160400192915050565b61034e806105996000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f260279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610249565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a2565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020d578251610206576001600160a01b0385163b6102065760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610217565b610217838361021f565b949350505050565b81511561022f5781518083602001fd5b8060405162461bcd60e51b81526004016101fd91906102be565b60006020828403121561025b57600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028d578181015183820152602001610275565b8381111561029c576000848401525b50505050565b600082516102b4818460208701610272565b9190910192915050565b60208152600082518060208401526102dd816040850160208701610272565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d51e81d3bc5ed20a26aeb05dce7e825c503b2061aa78628027300c8d65b9d89a64736f6c634300080c0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564";

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L37

File: src/contracts/pods/EigenPod.sol

167:       * this contract. It also verifies the current (not effective) balance  of the validator.  It verifies the provided proof of the ETH validator against the beacon chain state

376:                  // and the extra execution layer ETH in the contract is REQUIRED_BALANCE_GWEI, which must be withdrawn through EigenLayer's normal withdrawal process

379:                  // otherwise, just use the full withdrawal amount to continue to "back" the podOwner's remaining shares in EigenLayer (i.e. none is instantly withdrawable)

382:                  eigenPodManager.recordOvercommittedBeaconChainETH(podOwner, beaconChainETHStrategyIndex, uint256(REQUIRED_BALANCE_GWEI - withdrawalAmountGwei) * GWEI_TO_WEI);

390:                  // and the extra execution layer ETH in the contract is REQUIRED_BALANCE_GWEI, which must be withdrawn through EigenLayer's normal withdrawal process

393:                   * since in `verifyOvercommittedStake` the podOwner's beaconChainETH shares are decremented by `REQUIRED_BALANCE_WEI`, we must reverse the process here,

398:                  // otherwise, just use the full withdrawal amount to continue to "back" the podOwner's remaining shares in EigenLayer (i.e. none is instantly withdrawable)

401:                   * since in `verifyOvercommittedStake` the podOwner's beaconChainETH shares are decremented by `REQUIRED_BALANCE_WEI`, we must reverse the process here,

423:          require(!provenPartialWithdrawal[validatorIndex][withdrawalHappenedSlot], "EigenPod._processPartialWithdrawal: partial withdrawal has already been proven for this slot");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L167

[N‑20] Variable names that consist of all capital letters should be reserved for constant/immutable variables

If the variable needs to be different based on which class it comes from, a view/pure function should be used instead (e.g. like this).

There are 2 instances of this issue:

File: src/contracts/core/StrategyManagerStorage.sol

23:       bytes32 public DOMAIN_SEPARATOR;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManagerStorage.sol#L23

File: src/contracts/pods/EigenPod.sol

140:          uint256 _REQUIRED_BALANCE_WEI

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L140

[N‑21] Typos

There are 21 instances of this issue:

File: src/contracts/core/StrategyManager.sol

/// @audit syncupon
217:       *          where the token balance and corresponding strategy shares are not in syncupon reentrancy.

/// @audit targetting
242:       * targetting stakers who may be attempting to undelegate.

/// @audit syncupon
246:       *          where the token balance and corresponding strategy shares are not in syncupon reentrancy

/// @audit ECSDA
285:           * 1) if `staker` is an EOA, then `signature` must be a valid ECSDA signature from `staker`,

/// @audit WITHDRAWAL_WAITING_PERIOD
315:       * to accrue gains during the enforced WITHDRAWAL_WAITING_PERIOD.

/// @audit completeing
743:       * @notice Internal function for completeing the given `queuedWithdrawal`.

/// @audit succesfully
817:       * @notice Withdraws `amount` of virtual 'beaconChainETH' shares from `staker`, with any succesfully withdrawn funds going to `recipient`.

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L217

File: src/contracts/interfaces/IDelayedWithdrawalRouter.sol

/// @audit whethere
51:       /// @notice Convenience function for checking whethere or not the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array is currently claimable

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IDelayedWithdrawalRouter.sol#L51

File: src/contracts/interfaces/IDelegationManager.sol

/// @audit ECSDA
35:        * 1) if `staker` is an EOA, then `signature` is valid ECSDA signature from `staker`, indicating their intention for this action

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IDelegationManager.sol#L35

File: src/contracts/interfaces/IEigenPod.sol

/// @audit trackes
85:       ///@notice mapping that trackes proven partial withdrawals

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IEigenPod.sol#L85

File: src/contracts/interfaces/IPausable.sol

/// @audit goverance
29:        * It is permissioned to the `unpauser` address, which is expected to be a high threshold multisig or goverance contract.

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IPausable.sol#L29

File: src/contracts/interfaces/IStrategyManager.sol

/// @audit targetting
79:        * targetting stakers who may be attempting to undelegate.

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IStrategyManager.sol#L79

File: src/contracts/interfaces/IStrategy.sol

/// @audit signifcantly
35:        * @dev Implementation for these functions in particular may vary signifcantly for different strategies

/// @audit signifcantly
43:        * @dev Implementation for these functions in particular may vary signifcantly for different strategies

/// @audit signifcantly
57:        * @dev Implementation for these functions in particular may vary signifcantly for different strategies

/// @audit signifcantly
65:        * @dev Implementation for these functions in particular may vary signifcantly for different strategies

/// @audit underyling
75:       /// @notice The underyling token for shares in this Strategy

/// @audit thie
78:       /// @notice The total number of extant shares in thie Strategy

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IStrategy.sol#L35

File: src/contracts/libraries/BeaconChainProofs.sol

/// @audit exection
83:       // in exection payload header

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BeaconChainProofs.sol#L83

File: src/contracts/permissions/Pausable.sol

/// @audit goverance
88:        * It is permissioned to the `unpauser` address, which is expected to be a high threshold multisig or goverance contract.

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L88

File: src/contracts/pods/DelayedWithdrawalRouter.sol

/// @audit whethere
131:      /// @notice Convenience function for checking whethere or not the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array is currently claimable

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L131

[N‑22] Misplaced SPDX identifier

The SPDX identifier should be on the very first line of the file

There is one instance of this issue:

File: src/contracts/interfaces/IETHPOSDeposit.sol

1:    // ┏━━━┓━┏┓━┏┓━━┏━━━┓━━┏━━━┓━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━┏┓━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━━━━━━━┏┓━

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IETHPOSDeposit.sol#L1

[N‑23] File is missing NatSpec

There is one instance of this issue:

File: src/contracts/libraries/Endian.sol

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/Endian.sol

[N‑24] NatSpec is incomplete

There are 41 instances of this issue:

File: src/contracts/core/StrategyManager.sol

/// @audit Missing: '@param initialPausedStatus'
138       /**
139        * @notice Initializes the strategy manager contract. Sets the `pauserRegistry` (currently **not** modifiable after being set),
140        * and transfers contract ownership to the specified `initialOwner`.
141        * @param _pauserRegistry Used for access control of pausing.
142        * @param initialOwner Ownership of this contract is transferred to this address.
143        * @param initialStrategyWhitelister The initial value of `strategyWhitelister` to set. 
144        * @param _withdrawalDelayBlocks The initial value of `withdrawalDelayBlocks` to set.
145        */
146       function initialize(address initialOwner, address initialStrategyWhitelister, IPauserRegistry _pauserRegistry, uint256 initialPausedStatus, uint256 _withdrawalDelayBlocks)
147           external
148:          initializer

/// @audit Missing: '@return'
217        *          where the token balance and corresponding strategy shares are not in syncupon reentrancy.
218        */
219   
220       function depositIntoStrategy(IStrategy strategy, IERC20 token, uint256 amount)
221           external
222           onlyWhenNotPaused(PAUSED_DEPOSITS)
223           onlyNotFrozen(msg.sender)
224           nonReentrant
225:          returns (uint256 shares)

/// @audit Missing: '@return'
246        *          where the token balance and corresponding strategy shares are not in syncupon reentrancy
247        */
248       function depositIntoStrategyWithSignature(
249           IStrategy strategy,
250           IERC20 token,
251           uint256 amount,
252           address staker,
253           uint256 expiry,
254           bytes memory signature
255       )
256           external
257           onlyWhenNotPaused(PAUSED_DEPOSITS)
258           onlyNotFrozen(staker)
259           nonReentrant
260:          returns (uint256 shares)

/// @audit Missing: '@param withdrawer'
308       /**
309        * @notice Called by a staker to queue a withdrawal of the given amount of `shares` from each of the respective given `strategies`.
310        * @dev Stakers will complete their withdrawal by calling the 'completeQueuedWithdrawal' function.
311        * User shares are decreased in this function, but the total number of shares in each strategy remains the same.
312        * The total number of shares is decremented in the 'completeQueuedWithdrawal' function instead, which is where
313        * the funds are actually sent to the user through use of the strategies' 'withdrawal' function. This ensures
314        * that the value per share reported by each strategy will remain consistent, and that the shares will continue
315        * to accrue gains during the enforced WITHDRAWAL_WAITING_PERIOD.
316        * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
317        * for which `msg.sender` is withdrawing 100% of their shares
318        * @param strategies The Strategies to withdraw from
319        * @param shares The amount of shares to withdraw from each of the respective Strategies in the `strategies` array
320        * @dev Strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
321        * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
322        * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
323        * `stakerStrategyList` to lowest index
324        * @dev Note that if the withdrawal includes shares in the enshrined 'beaconChainETH' strategy, then it must *only* include shares in this strategy, and
325        * `withdrawer` must match the caller's address. The first condition is because slashing of queued withdrawals cannot be guaranteed 
326        * for Beacon Chain ETH (since we cannot trigger a withdrawal from the beacon chain through a smart contract) and the second condition is because shares in
327        * the enshrined 'beaconChainETH' strategy technically represent non-fungible positions (deposits to the Beacon Chain, each pointed at a specific EigenPod).
328        */
329       function queueWithdrawal(
330           uint256[] calldata strategyIndexes,
331           IStrategy[] calldata strategies,
332           uint256[] calldata shares,
333           address withdrawer,
334           bool undelegateIfPossible
335       )
336           external
337           onlyWhenNotPaused(PAUSED_WITHDRAWALS)
338           onlyNotFrozen(msg.sender)
339           nonReentrant
340:          returns (bytes32)

/// @audit Missing: '@param undelegateIfPossible'
308       /**
309        * @notice Called by a staker to queue a withdrawal of the given amount of `shares` from each of the respective given `strategies`.
310        * @dev Stakers will complete their withdrawal by calling the 'completeQueuedWithdrawal' function.
311        * User shares are decreased in this function, but the total number of shares in each strategy remains the same.
312        * The total number of shares is decremented in the 'completeQueuedWithdrawal' function instead, which is where
313        * the funds are actually sent to the user through use of the strategies' 'withdrawal' function. This ensures
314        * that the value per share reported by each strategy will remain consistent, and that the shares will continue
315        * to accrue gains during the enforced WITHDRAWAL_WAITING_PERIOD.
316        * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
317        * for which `msg.sender` is withdrawing 100% of their shares
318        * @param strategies The Strategies to withdraw from
319        * @param shares The amount of shares to withdraw from each of the respective Strategies in the `strategies` array
320        * @dev Strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
321        * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
322        * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
323        * `stakerStrategyList` to lowest index
324        * @dev Note that if the withdrawal includes shares in the enshrined 'beaconChainETH' strategy, then it must *only* include shares in this strategy, and
325        * `withdrawer` must match the caller's address. The first condition is because slashing of queued withdrawals cannot be guaranteed 
326        * for Beacon Chain ETH (since we cannot trigger a withdrawal from the beacon chain through a smart contract) and the second condition is because shares in
327        * the enshrined 'beaconChainETH' strategy technically represent non-fungible positions (deposits to the Beacon Chain, each pointed at a specific EigenPod).
328        */
329       function queueWithdrawal(
330           uint256[] calldata strategyIndexes,
331           IStrategy[] calldata strategies,
332           uint256[] calldata shares,
333           address withdrawer,
334           bool undelegateIfPossible
335       )
336           external
337           onlyWhenNotPaused(PAUSED_WITHDRAWALS)
338           onlyNotFrozen(msg.sender)
339           nonReentrant
340:          returns (bytes32)

/// @audit Missing: '@return'
308       /**
309        * @notice Called by a staker to queue a withdrawal of the given amount of `shares` from each of the respective given `strategies`.
310        * @dev Stakers will complete their withdrawal by calling the 'completeQueuedWithdrawal' function.
311        * User shares are decreased in this function, but the total number of shares in each strategy remains the same.
312        * The total number of shares is decremented in the 'completeQueuedWithdrawal' function instead, which is where
313        * the funds are actually sent to the user through use of the strategies' 'withdrawal' function. This ensures
314        * that the value per share reported by each strategy will remain consistent, and that the shares will continue
315        * to accrue gains during the enforced WITHDRAWAL_WAITING_PERIOD.
316        * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
317        * for which `msg.sender` is withdrawing 100% of their shares
318        * @param strategies The Strategies to withdraw from
319        * @param shares The amount of shares to withdraw from each of the respective Strategies in the `strategies` array
320        * @dev Strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
321        * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
322        * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
323        * `stakerStrategyList` to lowest index
324        * @dev Note that if the withdrawal includes shares in the enshrined 'beaconChainETH' strategy, then it must *only* include shares in this strategy, and
325        * `withdrawer` must match the caller's address. The first condition is because slashing of queued withdrawals cannot be guaranteed 
326        * for Beacon Chain ETH (since we cannot trigger a withdrawal from the beacon chain through a smart contract) and the second condition is because shares in
327        * the enshrined 'beaconChainETH' strategy technically represent non-fungible positions (deposits to the Beacon Chain, each pointed at a specific EigenPod).
328        */
329       function queueWithdrawal(
330           uint256[] calldata strategyIndexes,
331           IStrategy[] calldata strategies,
332           uint256[] calldata shares,
333           address withdrawer,
334           bool undelegateIfPossible
335       )
336           external
337           onlyWhenNotPaused(PAUSED_WITHDRAWALS)
338           onlyNotFrozen(msg.sender)
339           nonReentrant
340:          returns (bytes32)

/// @audit Missing: '@param strategies'
471       /**
472        * @notice Slashes the shares of a 'frozen' operator (or a staker delegated to one)
473        * @param slashedAddress is the frozen address that is having its shares slashed
474        * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
475        * for which `msg.sender` is withdrawing 100% of their shares
476        * @param recipient The slashed funds are withdrawn as tokens to this address.
477        * @dev strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
478        * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
479        * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
480        * `stakerStrategyList` to lowest index
481        */
482       function slashShares(
483           address slashedAddress,
484           address recipient,
485           IStrategy[] calldata strategies,
486           IERC20[] calldata tokens,
487           uint256[] calldata strategyIndexes,
488           uint256[] calldata shareAmounts
489       )
490           external
491           onlyOwner
492           onlyFrozen(slashedAddress)
493:          nonReentrant

/// @audit Missing: '@param tokens'
471       /**
472        * @notice Slashes the shares of a 'frozen' operator (or a staker delegated to one)
473        * @param slashedAddress is the frozen address that is having its shares slashed
474        * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
475        * for which `msg.sender` is withdrawing 100% of their shares
476        * @param recipient The slashed funds are withdrawn as tokens to this address.
477        * @dev strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
478        * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
479        * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
480        * `stakerStrategyList` to lowest index
481        */
482       function slashShares(
483           address slashedAddress,
484           address recipient,
485           IStrategy[] calldata strategies,
486           IERC20[] calldata tokens,
487           uint256[] calldata strategyIndexes,
488           uint256[] calldata shareAmounts
489       )
490           external
491           onlyOwner
492           onlyFrozen(slashedAddress)
493:          nonReentrant

/// @audit Missing: '@param shareAmounts'
471       /**
472        * @notice Slashes the shares of a 'frozen' operator (or a staker delegated to one)
473        * @param slashedAddress is the frozen address that is having its shares slashed
474        * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
475        * for which `msg.sender` is withdrawing 100% of their shares
476        * @param recipient The slashed funds are withdrawn as tokens to this address.
477        * @dev strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
478        * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
479        * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
480        * `stakerStrategyList` to lowest index
481        */
482       function slashShares(
483           address slashedAddress,
484           address recipient,
485           IStrategy[] calldata strategies,
486           IERC20[] calldata tokens,
487           uint256[] calldata strategyIndexes,
488           uint256[] calldata shareAmounts
489       )
490           external
491           onlyOwner
492           onlyFrozen(slashedAddress)
493:          nonReentrant

/// @audit Missing: '@param depositor'
650       /**
651        * @notice Internal function in which `amount` of ERC20 `token` is transferred from `msg.sender` to the Strategy-type contract
652        * `strategy`, with the resulting shares credited to `depositor`.
653        * @return shares The amount of *new* shares in `strategy` that have been credited to the `depositor`.
654        */
655       function _depositIntoStrategy(address depositor, IStrategy strategy, IERC20 token, uint256 amount)
656           internal
657           onlyStrategiesWhitelistedForDeposit(strategy)
658:          returns (uint256 shares)

/// @audit Missing: '@param strategy'
650       /**
651        * @notice Internal function in which `amount` of ERC20 `token` is transferred from `msg.sender` to the Strategy-type contract
652        * `strategy`, with the resulting shares credited to `depositor`.
653        * @return shares The amount of *new* shares in `strategy` that have been credited to the `depositor`.
654        */
655       function _depositIntoStrategy(address depositor, IStrategy strategy, IERC20 token, uint256 amount)
656           internal
657           onlyStrategiesWhitelistedForDeposit(strategy)
658:          returns (uint256 shares)

/// @audit Missing: '@param token'
650       /**
651        * @notice Internal function in which `amount` of ERC20 `token` is transferred from `msg.sender` to the Strategy-type contract
652        * `strategy`, with the resulting shares credited to `depositor`.
653        * @return shares The amount of *new* shares in `strategy` that have been credited to the `depositor`.
654        */
655       function _depositIntoStrategy(address depositor, IStrategy strategy, IERC20 token, uint256 amount)
656           internal
657           onlyStrategiesWhitelistedForDeposit(strategy)
658:          returns (uint256 shares)

/// @audit Missing: '@param amount'
650       /**
651        * @notice Internal function in which `amount` of ERC20 `token` is transferred from `msg.sender` to the Strategy-type contract
652        * `strategy`, with the resulting shares credited to `depositor`.
653        * @return shares The amount of *new* shares in `strategy` that have been credited to the `depositor`.
654        */
655       function _depositIntoStrategy(address depositor, IStrategy strategy, IERC20 token, uint256 amount)
656           internal
657           onlyStrategiesWhitelistedForDeposit(strategy)
658:          returns (uint256 shares)

/// @audit Missing: '@param depositor'
853       /**
854        * @notice Get all details on the depositor's deposits and corresponding shares
855        * @return (depositor's strategies, shares in these strategies)
856        */
857:      function getDeposits(address depositor) external view returns (IStrategy[] memory, uint256[] memory) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L138-L148

File: src/contracts/interfaces/IEigenPodManager.sol

/// @audit Missing: '@param beaconChainETHStrategyIndex'
38        /**
39         * @notice Removes beacon chain ETH from EigenLayer on behalf of the owner of an EigenPod, when the
40         *         balance of a validator is lower than how much stake they have committed to EigenLayer
41         * @param podOwner The owner of the pod whose balance must be removed.
42         * @param amount The amount of ETH to remove.
43         * @dev Callable only by the podOwner's EigenPod contract.
44         */
45:       function recordOvercommittedBeaconChainETH(address podOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) external;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IEigenPodManager.sol#L38-L45

File: src/contracts/interfaces/IEigenPod.sol

/// @audit Missing: '@param oracleBlockNumber'
126       /**
127        * @notice This function records a full withdrawal on behalf of one of the Ethereum validators for this EigenPod
128        * @param withdrawalProofs is the information needed to check the veracity of the block number and withdrawal being proven
129        * @param validatorFieldsProof is the proof of the validator's fields in the validator tree
130        * @param withdrawalFields are the fields of the withdrawal being proven
131        * @param validatorFields are the fields of the validator being proven
132        * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to 
133        *        the EigenPodManager to the StrategyManager in case it must be removed from the podOwner's list of strategies
134        */
135       function verifyAndProcessWithdrawal(
136           BeaconChainProofs.WithdrawalProofs calldata withdrawalProofs, 
137           bytes calldata validatorFieldsProof,
138           bytes32[] calldata validatorFields,
139           bytes32[] calldata withdrawalFields,
140           uint256 beaconChainETHStrategyIndex,
141:          uint64 oracleBlockNumber

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IEigenPod.sol#L126-L141

File: src/contracts/interfaces/ISlasher.sol

/// @audit Missing: '@param staker'
77        /**
78         * @notice Used to determine whether `staker` is actively 'frozen'. If a staker is frozen, then they are potentially subject to
79         * slashing of their funds, and cannot cannot deposit or withdraw from the strategyManager until the slashing process is completed
80         * and the staker's status is reset (to 'unfrozen').
81         * @return Returns 'true' if `staker` themselves has their status set to frozen, OR if the staker is delegated
82         * to an operator who has their status set to frozen. Otherwise returns 'false'.
83         */
84:       function isFrozen(address staker) external view returns (bool);

/// @audit Missing: '@return'
108        * @dev The correct `middlewareTimesIndex` input should be computable off-chain.
109        */
110:      function canWithdraw(address operator, uint32 withdrawalStartBlock, uint256 middlewareTimesIndex) external returns(bool);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/ISlasher.sol#L77-L84

File: src/contracts/interfaces/IStrategyManager.sol

/// @audit Missing: '@return'
41         * @dev Cannot be called by an address that is 'frozen' (this function will revert if the `msg.sender` is frozen).
42         */
43        function depositIntoStrategy(IStrategy strategy, IERC20 token, uint256 amount)
44            external
45:           returns (uint256);

/// @audit Missing: '@return'
80         * @dev Cannot be called on behalf of a staker that is 'frozen' (this function will revert if the `staker` is frozen).
81         */
82        function depositIntoStrategyWithSignature(
83            IStrategy strategy,
84            IERC20 token,
85            uint256 amount,
86            address staker,
87            uint256 expiry,
88            bytes memory signature
89        )
90            external
91:           returns (uint256 shares);

/// @audit Missing: '@param depositor'
96        /**
97         * @notice Get all details on the depositor's deposits and corresponding shares
98         * @return (depositor's strategies, shares in these strategies)
99         */
100:      function getDeposits(address depositor) external view returns (IStrategy[] memory, uint256[] memory);

/// @audit Missing: '@param withdrawer'
105       /**
106        * @notice Called by a staker to queue a withdrawal of the given amount of `shares` from each of the respective given `strategies`.
107        * @dev Stakers will complete their withdrawal by calling the 'completeQueuedWithdrawal' function.
108        * User shares are decreased in this function, but the total number of shares in each strategy remains the same.
109        * The total number of shares is decremented in the 'completeQueuedWithdrawal' function instead, which is where
110        * the funds are actually sent to the user through use of the strategies' 'withdrawal' function. This ensures
111        * that the value per share reported by each strategy will remain consistent, and that the shares will continue
112        * to accrue gains during the enforced WITHDRAWAL_WAITING_PERIOD.
113        * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
114        * for which `msg.sender` is withdrawing 100% of their shares
115        * @param strategies The Strategies to withdraw from
116        * @param shares The amount of shares to withdraw from each of the respective Strategies in the `strategies` array
117        * @dev Strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
118        * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
119        * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
120        * `stakerStrategyList` to lowest index
121        * @dev Note that if the withdrawal includes shares in the enshrined 'beaconChainETH' strategy, then it must *only* include shares in this strategy, and
122        * `withdrawer` must match the caller's address. The first condition is because slashing of queued withdrawals cannot be guaranteed 
123        * for Beacon Chain ETH (since we cannot trigger a withdrawal from the beacon chain through a smart contract) and the second condition is because shares in
124        * the enshrined 'beaconChainETH' strategy technically represent non-fungible positions (deposits to the Beacon Chain, each pointed at a specific EigenPod).
125        */
126       function queueWithdrawal(
127           uint256[] calldata strategyIndexes,
128           IStrategy[] calldata strategies,
129           uint256[] calldata shares,
130           address withdrawer,
131           bool undelegateIfPossible
132       )
133:          external returns(bytes32);

/// @audit Missing: '@param undelegateIfPossible'
105       /**
106        * @notice Called by a staker to queue a withdrawal of the given amount of `shares` from each of the respective given `strategies`.
107        * @dev Stakers will complete their withdrawal by calling the 'completeQueuedWithdrawal' function.
108        * User shares are decreased in this function, but the total number of shares in each strategy remains the same.
109        * The total number of shares is decremented in the 'completeQueuedWithdrawal' function instead, which is where
110        * the funds are actually sent to the user through use of the strategies' 'withdrawal' function. This ensures
111        * that the value per share reported by each strategy will remain consistent, and that the shares will continue
112        * to accrue gains during the enforced WITHDRAWAL_WAITING_PERIOD.
113        * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
114        * for which `msg.sender` is withdrawing 100% of their shares
115        * @param strategies The Strategies to withdraw from
116        * @param shares The amount of shares to withdraw from each of the respective Strategies in the `strategies` array
117        * @dev Strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
118        * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
119        * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
120        * `stakerStrategyList` to lowest index
121        * @dev Note that if the withdrawal includes shares in the enshrined 'beaconChainETH' strategy, then it must *only* include shares in this strategy, and
122        * `withdrawer` must match the caller's address. The first condition is because slashing of queued withdrawals cannot be guaranteed 
123        * for Beacon Chain ETH (since we cannot trigger a withdrawal from the beacon chain through a smart contract) and the second condition is because shares in
124        * the enshrined 'beaconChainETH' strategy technically represent non-fungible positions (deposits to the Beacon Chain, each pointed at a specific EigenPod).
125        */
126       function queueWithdrawal(
127           uint256[] calldata strategyIndexes,
128           IStrategy[] calldata strategies,
129           uint256[] calldata shares,
130           address withdrawer,
131           bool undelegateIfPossible
132       )
133:          external returns(bytes32);

/// @audit Missing: '@return'
105       /**
106        * @notice Called by a staker to queue a withdrawal of the given amount of `shares` from each of the respective given `strategies`.
107        * @dev Stakers will complete their withdrawal by calling the 'completeQueuedWithdrawal' function.
108        * User shares are decreased in this function, but the total number of shares in each strategy remains the same.
109        * The total number of shares is decremented in the 'completeQueuedWithdrawal' function instead, which is where
110        * the funds are actually sent to the user through use of the strategies' 'withdrawal' function. This ensures
111        * that the value per share reported by each strategy will remain consistent, and that the shares will continue
112        * to accrue gains during the enforced WITHDRAWAL_WAITING_PERIOD.
113        * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
114        * for which `msg.sender` is withdrawing 100% of their shares
115        * @param strategies The Strategies to withdraw from
116        * @param shares The amount of shares to withdraw from each of the respective Strategies in the `strategies` array
117        * @dev Strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
118        * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
119        * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
120        * `stakerStrategyList` to lowest index
121        * @dev Note that if the withdrawal includes shares in the enshrined 'beaconChainETH' strategy, then it must *only* include shares in this strategy, and
122        * `withdrawer` must match the caller's address. The first condition is because slashing of queued withdrawals cannot be guaranteed 
123        * for Beacon Chain ETH (since we cannot trigger a withdrawal from the beacon chain through a smart contract) and the second condition is because shares in
124        * the enshrined 'beaconChainETH' strategy technically represent non-fungible positions (deposits to the Beacon Chain, each pointed at a specific EigenPod).
125        */
126       function queueWithdrawal(
127           uint256[] calldata strategyIndexes,
128           IStrategy[] calldata strategies,
129           uint256[] calldata shares,
130           address withdrawer,
131           bool undelegateIfPossible
132       )
133:          external returns(bytes32);

/// @audit Missing: '@param strategies'
167       /**
168        * @notice Slashes the shares of a 'frozen' operator (or a staker delegated to one)
169        * @param slashedAddress is the frozen address that is having its shares slashed
170        * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
171        * for which `msg.sender` is withdrawing 100% of their shares
172        * @param recipient The slashed funds are withdrawn as tokens to this address.
173        * @dev strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
174        * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
175        * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
176        * `stakerStrategyList` to lowest index
177        */
178       function slashShares(
179           address slashedAddress,
180           address recipient,
181           IStrategy[] calldata strategies,
182           IERC20[] calldata tokens,
183           uint256[] calldata strategyIndexes,
184:          uint256[] calldata shareAmounts

/// @audit Missing: '@param tokens'
167       /**
168        * @notice Slashes the shares of a 'frozen' operator (or a staker delegated to one)
169        * @param slashedAddress is the frozen address that is having its shares slashed
170        * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
171        * for which `msg.sender` is withdrawing 100% of their shares
172        * @param recipient The slashed funds are withdrawn as tokens to this address.
173        * @dev strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
174        * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
175        * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
176        * `stakerStrategyList` to lowest index
177        */
178       function slashShares(
179           address slashedAddress,
180           address recipient,
181           IStrategy[] calldata strategies,
182           IERC20[] calldata tokens,
183           uint256[] calldata strategyIndexes,
184:          uint256[] calldata shareAmounts

/// @audit Missing: '@param shareAmounts'
167       /**
168        * @notice Slashes the shares of a 'frozen' operator (or a staker delegated to one)
169        * @param slashedAddress is the frozen address that is having its shares slashed
170        * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
171        * for which `msg.sender` is withdrawing 100% of their shares
172        * @param recipient The slashed funds are withdrawn as tokens to this address.
173        * @dev strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
174        * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
175        * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
176        * `stakerStrategyList` to lowest index
177        */
178       function slashShares(
179           address slashedAddress,
180           address recipient,
181           IStrategy[] calldata strategies,
182           IERC20[] calldata tokens,
183           uint256[] calldata strategyIndexes,
184:          uint256[] calldata shareAmounts

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IStrategyManager.sol#L41-L45

File: src/contracts/interfaces/IStrategy.sol

/// @audit Missing: '@param depositor'
22        /**
23         * @notice Used to withdraw tokens from this Strategy, to the `depositor`'s address
24         * @param token is the ERC20 token being transferred out
25         * @param amountShares is the amount of shares being withdrawn
26         * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's
27         * other functions, and individual share balances are recorded in the strategyManager as well.
28         */
29:       function withdraw(address depositor, IERC20 token, uint256 amountShares) external;

/// @audit Missing: '@return'
35         * @dev Implementation for these functions in particular may vary signifcantly for different strategies
36         */
37:       function sharesToUnderlying(uint256 amountShares) external returns (uint256);

/// @audit Missing: '@return'
43         * @dev Implementation for these functions in particular may vary signifcantly for different strategies
44         */
45:       function underlyingToShares(uint256 amountUnderlying) external returns (uint256);

/// @audit Missing: '@return'
57         * @dev Implementation for these functions in particular may vary signifcantly for different strategies
58         */
59:       function sharesToUnderlyingView(uint256 amountShares) external view returns (uint256);

/// @audit Missing: '@return'
65         * @dev Implementation for these functions in particular may vary signifcantly for different strategies
66         */
67:       function underlyingToSharesView(uint256 amountUnderlying) external view returns (uint256);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IStrategy.sol#L22-L29

File: src/contracts/libraries/BeaconChainProofs.sol

/// @audit Missing: '@return'
176        * @param balanceRoot is the combination of 4 validator balances being proven for.
177        */
178:     function getBalanceFromBalanceRoot(uint40 validatorIndex, bytes32 balanceRoot) internal pure returns (uint64) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BeaconChainProofs.sol#L176-L178

File: src/contracts/libraries/Merkle.sol

/// @audit Missing: '@return'
127        @notice requires the leaves.length is a power of 2
128        */ 
129       function merkleizeSha256(
130           bytes32[] memory leaves
131:      ) internal pure returns (bytes32) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/Merkle.sol#L127-L131

File: src/contracts/pods/EigenPodManager.sol

/// @audit Missing: '@param beaconChainETHStrategyIndex'
132       /**
133        * @notice Removes beacon chain ETH from EigenLayer on behalf of the owner of an EigenPod, when the
134        *         balance of a validator is lower than how much stake they have committed to EigenLayer
135        * @param podOwner The owner of the pod whose balance must be removed.
136        * @param amount The amount of beacon chain ETH to decrement from the podOwner's shares in the strategyManager.
137        * @dev Callable only by the podOwner's EigenPod contract.
138        */
139:      function recordOvercommittedBeaconChainETH(address podOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) external onlyEigenPod(podOwner) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L132-L139

File: src/contracts/pods/EigenPod.sol

/// @audit Missing: '@param oracleBlockNumber'
296       /**
297        * @notice This function records a full withdrawal on behalf of one of the Ethereum validators for this EigenPod
298        * @param withdrawalProofs is the information needed to check the veracity of the block number and withdrawal being proven
299        * @param validatorFieldsProof is the information needed to check the veracity of the validator fields being proven
300        * @param withdrawalFields are the fields of the withdrawal being proven
301        * @param validatorFields are the fields of the validator being proven
302        * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to 
303        *        the EigenPodManager to the StrategyManager in case it must be removed from the podOwner's list of strategies
304        */
305       function verifyAndProcessWithdrawal(
306           BeaconChainProofs.WithdrawalProofs calldata withdrawalProofs, 
307           bytes calldata validatorFieldsProof,
308           bytes32[] calldata validatorFields,
309           bytes32[] calldata withdrawalFields,
310           uint256 beaconChainETHStrategyIndex,
311           uint64 oracleBlockNumber
312       )
313           external
314           onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_WITHDRAWAL)
315           onlyNotFrozen
316           /** 
317            * Check that the provided block number being proven against is after the `mostRecentWithdrawalBlockNumber`.
318            * Without this check, there is an edge case where a user proves a past withdrawal for a validator whose funds they already withdrew,
319            * as a way to "withdraw the same funds twice" without providing adequate proof.
320            * Note that this check is not made using the oracleBlockNumber as in the `verifyWithdrawalCredentials` proof; instead this proof
321            * proof is made for the block number of the withdrawal, which may be within 8192 slots of the oracleBlockNumber. 
322            * This difference in modifier usage is OK, since it is still not possible to `verifyAndProcessWithdrawal` against a slot that occurred
323            * *prior* to the proof provided in the `verifyWithdrawalCredentials` function.
324            */
325:          proofIsForValidBlockNumber(Endian.fromLittleEndianUint64(withdrawalProofs.blockNumberRoot))

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L296-L325

File: src/contracts/strategies/StrategyBase.sol

/// @audit Missing: '@param depositor'
114       /**
115        * @notice Used to withdraw tokens from this Strategy, to the `depositor`'s address
116        * @param token is the ERC20 token being transferred out
117        * @param amountShares is the amount of shares being withdrawn
118        * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's
119        * other functions, and individual share balances are recorded in the strategyManager as well.
120        */
121       function withdraw(address depositor, IERC20 token, uint256 amountShares)
122           external
123           virtual
124           override
125           onlyWhenNotPaused(PAUSED_WITHDRAWALS)
126:          onlyStrategyManager

/// @audit Missing: '@return'
170        * @dev Implementation for these functions in particular may vary signifcantly for different strategies
171        */
172:      function sharesToUnderlyingView(uint256 amountShares) public view virtual override returns (uint256) {

/// @audit Missing: '@return'
184        * @dev Implementation for these functions in particular may vary signifcantly for different strategies
185        */
186:      function sharesToUnderlying(uint256 amountShares) public view virtual override returns (uint256) {

/// @audit Missing: '@return'
194        * @dev Implementation for these functions in particular may vary signifcantly for different strategies
195        */
196:      function underlyingToSharesView(uint256 amountUnderlying) public view virtual returns (uint256) {

/// @audit Missing: '@return'
209        * @dev Implementation for these functions in particular may vary signifcantly for different strategies
210        */
211:      function underlyingToShares(uint256 amountUnderlying) external view virtual returns (uint256) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L114-L126

[N‑25] Event is missing indexed fields

Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.

There are 23 instances of this issue:

File: src/contracts/core/StrategyManager.sol

54        event Deposit(
55            address depositor, IERC20 token, IStrategy strategy, uint256 shares
56:       );

65        event ShareWithdrawalQueued(
66            address depositor, uint96 nonce, IStrategy strategy, uint256 shares
67:       );

77        event WithdrawalQueued(
78            address depositor, uint96 nonce, address withdrawer, address delegatedAddress, bytes32 withdrawalRoot
79:       );

82:       event WithdrawalCompleted(address indexed depositor, uint96 nonce, address indexed withdrawer, bytes32 withdrawalRoot);

85:       event StrategyWhitelisterChanged(address previousAddress, address newAddress);

88:       event StrategyAddedToDepositWhitelist(IStrategy strategy);

91:       event StrategyRemovedFromDepositWhitelist(IStrategy strategy);

94:       event WithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L54-L56

File: src/contracts/interfaces/IETHPOSDeposit.sol

19:       event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IETHPOSDeposit.sol#L19

File: src/contracts/permissions/Pausable.sol

26:       event Paused(address indexed account, uint256 newPausedStatus);

29:       event Unpaused(address indexed account, uint256 newPausedStatus);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L26

File: src/contracts/permissions/PauserRegistry.sol

17:       event PauserChanged(address previousPauser, address newPauser);

19:       event UnpauserChanged(address previousUnpauser, address newUnpauser);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/PauserRegistry.sol#L17

File: src/contracts/pods/DelayedWithdrawalRouter.sol

13:       event WithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue);

33:       event DelayedWithdrawalCreated(address podOwner, address recipient, uint256 amount, uint256 index);

36:       event DelayedWithdrawalsClaimed(address recipient, uint256 amountClaimed, uint256 delayedWithdrawalsCompleted);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L13

File: src/contracts/pods/EigenPodManager.sol

64:       event BeaconChainETHDeposited(address indexed podOwner, uint256 amount);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L64

File: src/contracts/pods/EigenPod.sol

82:       event EigenPodStaked(bytes pubkey);

85:       event ValidatorRestaked(uint40 validatorIndex);

88:       event ValidatorOvercommitted(uint40 validatorIndex);

91:       event FullWithdrawalRedeemed(uint40 validatorIndex, address indexed recipient, uint64 withdrawalAmountGwei);

94:       event PartialWithdrawalRedeemed(uint40 validatorIndex, address indexed recipient, uint64 partialWithdrawalAmountGwei);

97:       event RestakedBeaconChainETHWithdrawn(address indexed recipient, uint256 amount);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L82

[N‑26] Consider using delete rather than assigning zero to clear values

The delete keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic

There is one instance of this issue:

File: src/contracts/core/StrategyManager.sol

825:                  beaconChainETHSharesToDecrementOnWithdrawal[staker] = 0;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L825

[N‑27] Avoid the use of sensitive terms

Use alternative variants, e.g. allowlist/denylist instead of whitelist/blacklist

There are 31 instances of this issue:

File: src/contracts/core/StrategyManager.sol

146:      function initialize(address initialOwner, address initialStrategyWhitelister, IPauserRegistry _pauserRegistry, uint256 initialPausedStatus, uint256 _withdrawalDelayBlocks)

846:      function _setStrategyWhitelister(address newStrategyWhitelister) internal {

592:      function addStrategiesToDepositWhitelist(IStrategy[] calldata strategiesToWhitelist) external onlyStrategyWhitelister {

593:          uint256 strategiesToWhitelistLength = strategiesToWhitelist.length;

607:      function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external onlyStrategyWhitelister {

608:          uint256 strategiesToRemoveFromWhitelistLength = strategiesToRemoveFromWhitelist.length;

587:      function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner {

592:      function addStrategiesToDepositWhitelist(IStrategy[] calldata strategiesToWhitelist) external onlyStrategyWhitelister {

607:      function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external onlyStrategyWhitelister {

846:      function _setStrategyWhitelister(address newStrategyWhitelister) internal {

84:       /// @notice Emitted when the `strategyWhitelister` is changed

143:       * @param initialStrategyWhitelister The initial value of `strategyWhitelister` to set. 

586:      /// @notice Owner-only function to change the `strategyWhitelister` address.

591:      /// @notice Owner-only function that adds the provided Strategies to the 'whitelist' of strategies that stakers can deposit into

595:              // change storage and emit event only if strategy is not already in whitelist

606:      /// @notice Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers can deposit into

610:              // change storage and emit event only if strategy is already in whitelist

845:      /// @notice Internal function for modifying the `strategyWhitelister`. Used inside of the `setStrategyWhitelister` and `initialize` functions.

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L146

File: src/contracts/core/StrategyManagerStorage.sol

36:       address public strategyWhitelister;

57:       mapping(IStrategy => bool) public strategyIsWhitelistedForDeposit;

35:       /// @notice Permissioned role, which can be changed by the contract owner. Has the ability to edit the strategy whitelist

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManagerStorage.sol#L36

File: src/contracts/interfaces/ISlasher.sol

133:      function operatorWhitelistedContractsLinkedListSize(address operator) external view returns (uint256);

136:      function operatorWhitelistedContractsLinkedListEntry(address operator, address node) external view returns (bool, uint256, uint256);

132:      /// @notice Getter function for fetching `_operatorToWhitelistedContractsByUpdate[operator].size`.

135:      /// @notice Getter function for fetching a single node in the operator's linked list (`_operatorToWhitelistedContractsByUpdate[operator]`).

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/ISlasher.sol#L133

File: src/contracts/interfaces/IStrategyManager.sol

210:      function addStrategiesToDepositWhitelist(IStrategy[] calldata strategiesToWhitelist) external;

213:      function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external;

210:      function addStrategiesToDepositWhitelist(IStrategy[] calldata strategiesToWhitelist) external;

213:      function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external;

209:      /// @notice Owner-only function that adds the provided Strategies to the 'whitelist' of strategies that stakers can deposit into

212:      /// @notice Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers can deposit into

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IStrategyManager.sol#L210

[N‑28] Contracts should have full test coverage

While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit.

There is one instance of this issue:

File: Various Files

[N‑29] Large or complicated code bases should implement invariant tests

Large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts, should implement invariant fuzzing tests. Invariant fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold. Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and invariant fuzzers, with properly and extensively-written invariants, can close this testing gap significantly.

There is one instance of this issue:

File: Various Files

[N‑30] Function ordering does not follow the Solidity style guide

According to the Solidity style guide, functions should be laid out in the following order :constructor(), receive(), fallback(), external, public, internal, private, but the cases below do not follow this pattern

There are 7 instances of this issue:

File: src/contracts/core/StrategyManager.sol

/// @audit _setStrategyWhitelister() came earlier
857:      function getDeposits(address depositor) external view returns (IStrategy[] memory, uint256[] memory) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L857

File: src/contracts/permissions/Pausable.sol

/// @audit _initializePauser() came earlier
71:       function pause(uint256 newPausedStatus) external onlyPauser {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L71

File: src/contracts/pods/EigenPodManager.sol

/// @audit _updateBeaconChainOracle() came earlier
193:      function getPod(address podOwner) public view returns (IEigenPod) {

/// @audit hasPod() came earlier
215:      function getBeaconChainStateRoot(uint64 blockNumber) external view returns(bytes32) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L193

File: src/contracts/pods/EigenPod.sol

/// @audit _processPartialWithdrawal() came earlier
437       function withdrawRestakedBeaconChainETH(
438           address recipient,
439           uint256 amountWei
440       )
441           external
442:          onlyEigenPodManager

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L437-L442

File: src/contracts/strategies/StrategyBase.sol

/// @audit _initializeStrategyBase() came earlier
78        function deposit(IERC20 token, uint256 amount)
79            external
80            virtual
81            override
82            onlyWhenNotPaused(PAUSED_DEPOSITS)
83            onlyStrategyManager
84:           returns (uint256 newShares)

/// @audit underlyingToSharesView() came earlier
211:      function underlyingToShares(uint256 amountUnderlying) external view virtual returns (uint256) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L78-L84

[N‑31] Contract does not follow the Solidity style guide's suggested layout ordering

The style guide says that, within a contract, the ordering should be 1) Type declarations, 2) State variables, 3) Events, 4) Modifiers, and 5) Functions, but the contract(s) below do not follow this ordering

There are 7 instances of this issue:

File: src/contracts/core/StrategyManagerStorage.sol

/// @audit function constructor came earlier
83:       uint256[40] private __gap;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManagerStorage.sol#L83

File: src/contracts/permissions/Pausable.sol

/// @audit function paused came earlier
115:      uint256[48] private __gap;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L115

File: src/contracts/pods/DelayedWithdrawalRouter.sol

/// @audit event WithdrawalDelayBlocksSet came earlier
16:       uint8 internal constant PAUSED_DELAYED_WITHDRAWAL_CLAIMS = 0;

/// @audit function _setWithdrawalDelayBlocks came earlier
177:      uint256[48] private __gap;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L16

File: src/contracts/pods/EigenPodManager.sol

/// @audit function getBeaconChainStateRoot came earlier
226:      uint256[48] private __gap;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L226

File: src/contracts/pods/EigenPod.sol

/// @audit function _sendETH came earlier
473:      uint256[46] private __gap;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L473

File: src/contracts/strategies/StrategyBase.sol

/// @audit function _tokenBalance came earlier
250:      uint256[48] private __gap;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L250

Gas Optimizations

[G‑01] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

There is one instance of this issue:

File: src/contracts/core/StrategyManagerStorage.sol

49        mapping(address => mapping(IStrategy => uint256)) public stakerStrategyShares;
50        /// @notice Mapping: staker => array of strategies in which they have nonzero shares
51:       mapping(address => IStrategy[]) public stakerStrategyList;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManagerStorage.sol#L49-L51

[G‑02] State variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

There are 15 instances of this issue:

File: src/contracts/core/StrategyManager.sol

/// @audit stakerStrategyList[depositor].length on line 720
723:              uint256 stratsLength = stakerStrategyList[depositor].length;

/// @audit stakerStrategyList[depositor].length on line 723
728:                      stakerStrategyList[depositor][j] = stakerStrategyList[depositor][stakerStrategyList[depositor].length - 1];

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L723

File: src/contracts/permissions/Pausable.sol

/// @audit _paused on line 73
73:           require((_paused & newPausedStatus) == _paused, "Pausable.pause: invalid attempt to unpause functionality");

/// @audit _paused on line 94
94:           require(((~_paused) & (~newPausedStatus)) == (~_paused), "Pausable.unpause: invalid attempt to pause functionality");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L73

File: src/contracts/pods/DelayedWithdrawalRouter.sol

/// @audit _userWithdrawals[recipient].delayedWithdrawals on line 66
67:               emit DelayedWithdrawalCreated(podOwner, recipient, withdrawalAmount, _userWithdrawals[recipient].delayedWithdrawals.length - 1);

/// @audit _userWithdrawals[user].delayedWithdrawals on line 112
116:              claimableDelayedWithdrawals[i] = _userWithdrawals[user].delayedWithdrawals[delayedWithdrawalsCompleted + i];

/// @audit _userWithdrawals[recipient].delayedWithdrawals on line 140
144:              DelayedWithdrawal memory delayedWithdrawal = _userWithdrawals[recipient].delayedWithdrawals[delayedWithdrawalsCompletedBefore + i];

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L67

File: src/contracts/pods/EigenPod.sol

/// @audit podOwner on line 355
357:              _processPartialWithdrawal(slot, withdrawalAmountGwei, validatorIndex, podOwner);

/// @audit podOwner on line 382
396:                  eigenPodManager.restakeBeaconChainETH(podOwner, REQUIRED_BALANCE_WEI);

/// @audit podOwner on line 396
404:                  eigenPodManager.restakeBeaconChainETH(podOwner, uint256(withdrawalAmountGwei) * GWEI_TO_WEI);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L357

File: src/contracts/strategies/StrategyBase.sol

/// @audit underlyingToken on line 128
155:          underlyingToken.safeTransfer(depositor, amountToSend);

/// @audit totalShares on line 92
99:                   newShares = (amount * totalShares) / priorTokenBalance;

/// @audit totalShares on line 99
105:          uint256 updatedTotalShares = totalShares + newShares;

/// @audit totalShares on line 173
176:              return (_tokenBalance() * amountShares) / totalShares;

/// @audit totalShares on line 198
201:              return (amountUnderlying * totalShares) / tokenBalance;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L155

[G‑03] Multiple accesses of a mapping/array should use a local variable cache

The instances below point to the second+ access of a value inside a mapping/array, within a function. Caching a mapping's value in a local storage or calldata variable when the value is accessed multiple times, saves ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations. Caching an array's struct avoids recalculating the array offsets into memory/calldata

There are 11 instances of this issue:

File: src/contracts/core/StrategyManager.sol

/// @audit stakerStrategyList[depositor] on line 637
640:              stakerStrategyList[depositor].push(strategy);

/// @audit stakerStrategyList[depositor] on line 720
723:              uint256 stratsLength = stakerStrategyList[depositor].length;

/// @audit stakerStrategyList[depositor] on line 723
728:                      stakerStrategyList[depositor][j] = stakerStrategyList[depositor][stakerStrategyList[depositor].length - 1];

/// @audit stakerStrategyList[depositor] on line 728
739:          stakerStrategyList[depositor].pop();

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L640

File: src/contracts/pods/DelayedWithdrawalRouter.sol

/// @audit _userWithdrawals[recipient] on line 66
67:               emit DelayedWithdrawalCreated(podOwner, recipient, withdrawalAmount, _userWithdrawals[recipient].delayedWithdrawals.length - 1);

/// @audit _userWithdrawals[user] on line 111
112:          uint256 delayedWithdrawalsLength = _userWithdrawals[user].delayedWithdrawals.length;

/// @audit _userWithdrawals[user] on line 112
116:              claimableDelayedWithdrawals[i] = _userWithdrawals[user].delayedWithdrawals[delayedWithdrawalsCompleted + i];

/// @audit _userWithdrawals[user] on line 133
133:          return ((index >= _userWithdrawals[user].delayedWithdrawalsCompleted) && (block.number >= _userWithdrawals[user].delayedWithdrawals[index].blockCreated + withdrawalDelayBlocks));

/// @audit _userWithdrawals[recipient] on line 139
140:          uint256 _userWithdrawalsLength = _userWithdrawals[recipient].delayedWithdrawals.length;

/// @audit _userWithdrawals[recipient] on line 140
144:              DelayedWithdrawal memory delayedWithdrawal = _userWithdrawals[recipient].delayedWithdrawals[delayedWithdrawalsCompletedBefore + i];

/// @audit _userWithdrawals[recipient] on line 144
157:          _userWithdrawals[recipient].delayedWithdrawalsCompleted = delayedWithdrawalsCompletedBefore + i;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L67

[G‑04] <x> += <y> costs more gas than <x> = <x> + <y> for state variables

Using the addition operator instead of plus-equals saves 113 gas

There are 5 instances of this issue:

File: src/contracts/pods/EigenPod.sol

377:                  restakedExecutionLayerGwei += REQUIRED_BALANCE_GWEI;

380:                  restakedExecutionLayerGwei += withdrawalAmountGwei;

391:                  restakedExecutionLayerGwei += REQUIRED_BALANCE_GWEI;

399:                  restakedExecutionLayerGwei += withdrawalAmountGwei;

445:          restakedExecutionLayerGwei -= uint64(amountWei / GWEI_TO_WEI);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L377

[G‑05] internal functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

There are 4 instances of this issue:

File: src/contracts/core/StrategyManager.sol

715:      function _removeStrategyFromStakerStrategyList(address depositor, uint256 strategyIndex, IStrategy strategy) internal {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L715

File: src/contracts/pods/EigenPod.sol

361       function _processFullWithdrawal(
362           uint64 withdrawalAmountGwei,
363           uint40 validatorIndex,
364           uint256 beaconChainETHStrategyIndex,
365           address recipient,
366:          VALIDATOR_STATUS status

422:      function _processPartialWithdrawal(uint64 withdrawalHappenedSlot, uint64 partialWithdrawalAmountGwei, uint40 validatorIndex, address recipient) internal {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L361-L366

File: src/contracts/strategies/StrategyBase.sol

56:       function _initializeStrategyBase(IERC20 _underlyingToken, IPauserRegistry _pauserRegistry) internal onlyInitializing {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L56

[G‑06] Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement

require(a <= b); x = b - a => require(a <= b); unchecked { x = b - a }

There are 5 instances of this issue:

File: src/contracts/core/StrategyManager.sol

/// @audit require() on line 690
693:              userShares = userShares - shareAmount;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L693

File: src/contracts/strategies/StrategyBase.sol

/// @audit require() on line 132
137:          uint256 updatedTotalShares = priorTotalShares - amountShares;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L137

File: src/contracts/core/StrategyManager.sol

/// @audit if-condition on line 190
191:              uint256 debt = amount - userShares;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L191

File: src/contracts/pods/EigenPod.sol

/// @audit if-condition on line 373
375:                  amountToSend = uint256(withdrawalAmountGwei - REQUIRED_BALANCE_GWEI) * uint256(GWEI_TO_WEI);

/// @audit if-condition on line 387
389:                  amountToSend = uint256(withdrawalAmountGwei - REQUIRED_BALANCE_GWEI) * uint256(GWEI_TO_WEI);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L375

[G‑07] <array>.length should not be looked up in every loop of a for-loop

The overheads outlined below are PER LOOP, excluding the first loop

  • storage arrays incur a Gwarmaccess (100 gas)
  • memory arrays use MLOAD (3 gas)
  • calldata arrays use CALLDATALOAD (3 gas)

Caching the length changes each of these to a DUP<N> (3 gas), and gets rid of the extra DUP<N> needed to store the stack offset

There are 4 instances of this issue:

File: src/contracts/core/StrategyManager.sol

358:          for (uint256 i = 0; i < strategies.length;) {

466:          for(uint256 i = 0; i < queuedWithdrawals.length; i++) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L358

File: src/contracts/libraries/Merkle.sol

50:           for (uint256 i = 32; i <= proof.length; i+=32) {

101:          for (uint256 i = 32; i <= proof.length; i+=32) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/Merkle.sol#L50

[G‑08] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops

The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop

There are 8 instances of this issue:

File: src/contracts/core/StrategyManager.sol

466:          for(uint256 i = 0; i < queuedWithdrawals.length; i++) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L466

File: src/contracts/libraries/BeaconChainProofs.sol

133:          for (uint256 i = 0; i < NUM_BEACON_BLOCK_HEADER_FIELDS; ++i) {

143:          for (uint256 i = 0; i < NUM_BEACON_STATE_FIELDS; ++i) {

153:          for (uint256 i = 0; i < NUM_VALIDATOR_FIELDS; ++i) {

163:          for (uint256 i = 0; i < ETH1_DATA_FIELD_TREE_HEIGHT; ++i) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BeaconChainProofs.sol#L133

File: src/contracts/libraries/Merkle.sol

137:          for (uint i = 0; i < numNodesInLayer; i++) {

145:              for (uint i = 0; i < numNodesInLayer; i++) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/Merkle.sol#L137

File: src/contracts/pods/DelayedWithdrawalRouter.sol

115:          for (uint256 i = 0; i < claimableDelayedWithdrawalsLength; i++) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L115

[G‑09] require()/revert() strings longer than 32 bytes cost extra gas

Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas

There are 86 instances of this issue:

File: src/contracts/core/StrategyManager.sol

97            require(
98                !slasher.isFrozen(staker),
99                "StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing"
100:          );

105:          require(slasher.isFrozen(staker), "StrategyManager.onlyFrozen: staker has not been frozen");

110:          require(address(eigenPodManager) == msg.sender, "StrategyManager.onlyEigenPodManager: not the eigenPodManager");

115:          require(msg.sender == strategyWhitelister, "StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister");

120:          require(strategyIsWhitelistedForDeposit[strategy], "StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted");

262           require(
263               expiry >= block.timestamp,
264               "StrategyManager.depositIntoStrategyWithSignature: signature expired"
265:          );

290               require(IERC1271(staker).isValidSignature(digestHash, signature) == ERC1271_MAGICVALUE,
291:                  "StrategyManager.depositIntoStrategyWithSignature: ERC1271 signature verification failed");

293               require(ECDSA.recover(digestHash, signature) == staker,
294:                  "StrategyManager.depositIntoStrategyWithSignature: signature not from staker");

342:          require(strategies.length == shares.length, "StrategyManager.queueWithdrawal: input length mismatch");

343:          require(withdrawer != address(0), "StrategyManager.queueWithdrawal: cannot withdraw to zero address");

360                   require(withdrawer == msg.sender,
361:                      "StrategyManager.queueWithdrawal: cannot queue a withdrawal of Beacon Chain ETH to a different address");

362                   require(strategies.length == 1,
363:                      "StrategyManager.queueWithdrawal: cannot queue a withdrawal including Beacon Chain ETH and other tokens");

364                   require(shares[i] % GWEI_TO_WEI == 0,
365:                      "StrategyManager.queueWithdrawal: cannot queue a withdrawal of Beacon Chain ETH for an non-whole amount of gwei");

495:          require(tokens.length == strategies.length, "StrategyManager.slashShares: input length mismatch");

542:          require(tokens.length == queuedWithdrawal.strategies.length, "StrategyManager.slashQueuedWithdrawal: input length mismatch");

548           require(
549               withdrawalRootPending[withdrawalRoot],
550               "StrategyManager.slashQueuedWithdrawal: withdrawal is not pending"
551:          );

631:          require(depositor != address(0), "StrategyManager._addShares: depositor cannot be zero address");

632:          require(shares != 0, "StrategyManager._addShares: shares should not be zero!");

636               require(
637                   stakerStrategyList[depositor].length < MAX_STAKER_STRATEGY_LIST_LENGTH,
638                   "StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH"
639:              );

684:          require(depositor != address(0), "StrategyManager._removeShares: depositor cannot be zero address");

685:          require(shareAmount != 0, "StrategyManager._removeShares: shareAmount should not be zero!");

690:          require(shareAmount <= userShares, "StrategyManager._removeShares: shareAmount too high");

736:              require(j != stratsLength, "StrategyManager._removeStrategyFromStakerStrategyList: strategy not found");

750           require(
751               withdrawalRootPending[withdrawalRoot],
752               "StrategyManager.completeQueuedWithdrawal: withdrawal is not pending"
753:          );

755           require(
756               slasher.canWithdraw(queuedWithdrawal.delegatedAddress, queuedWithdrawal.withdrawalStartBlock, middlewareTimesIndex),
757               "StrategyManager.completeQueuedWithdrawal: shares pending withdrawal are still slashable"
758:          );

761           require(queuedWithdrawal.withdrawalStartBlock + withdrawalDelayBlocks <= block.number 
762                   || queuedWithdrawal.strategies[0] == beaconChainETHStrategy,
763               "StrategyManager.completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed"
764:          );

766           require(
767               msg.sender == queuedWithdrawal.withdrawerAndNonce.withdrawer,
768               "StrategyManager.completeQueuedWithdrawal: only specified withdrawer can complete a queued withdrawal"
769:          );

778:              require(tokens.length == queuedWithdrawal.strategies.length, "StrategyManager.completeQueuedWithdrawal: input length mismatch");

812:          require(stakerStrategyList[depositor].length == 0, "StrategyManager._undelegate: depositor has active deposits");

840:          require(_withdrawalDelayBlocks <= MAX_WITHDRAWAL_DELAY_BLOCKS, "StrategyManager.setWithdrawalDelay: _withdrawalDelayBlocks too high");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L97-L100

File: src/contracts/libraries/BeaconChainProofs.sol

199:          require(validatorFields.length == 2**VALIDATOR_FIELD_TREE_HEIGHT, "BeaconChainProofs.verifyValidatorFields: Validator fields has incorrect length");

205:          require(proof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorFields: Proof has incorrect length");

211:          require(Merkle.verifyInclusionSha256(proof, beaconStateRoot, validatorRoot, index), "BeaconChainProofs.verifyValidatorFields: Invalid merkle proof");

227:          require(proof.length == 32 * ((BALANCE_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorBalance: Proof has incorrect length");

236:          require(Merkle.verifyInclusionSha256(proof, beaconStateRoot, balanceRoot, balanceIndex), "BeaconChainProofs.verifyValidatorBalance: Invalid merkle proof");

250:          require(withdrawalFields.length == 2**WITHDRAWAL_FIELD_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: withdrawalFields has incorrect length");

252:          require(proofs.blockHeaderRootIndex < 2**BLOCK_ROOTS_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: blockRootIndex is too large");

253:          require(proofs.withdrawalIndex < 2**WITHDRAWALS_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: withdrawalIndex is too large");

256           require(proofs.blockHeaderProof.length == 32 * (BEACON_STATE_FIELD_TREE_HEIGHT + BLOCK_ROOTS_TREE_HEIGHT),
257:              "BeaconChainProofs.verifyWithdrawalProofs: blockHeaderProof has incorrect length");

258           require(proofs.withdrawalProof.length == 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT + WITHDRAWALS_TREE_HEIGHT + 1),
259:              "BeaconChainProofs.verifyWithdrawalProofs: withdrawalProof has incorrect length");

260           require(proofs.executionPayloadProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT),
261:              "BeaconChainProofs.verifyWithdrawalProofs: executionPayloadProof has incorrect length");

269           require(Merkle.verifyInclusionSha256(proofs.blockHeaderProof, beaconStateRoot, proofs.blockHeaderRoot, blockHeaderIndex),
270:              "BeaconChainProofs.verifyWithdrawalProofs: Invalid block header merkle proof");

273:          require(Merkle.verifyInclusionSha256(proofs.slotProof, proofs.blockHeaderRoot, proofs.slotRoot, SLOT_INDEX), "BeaconChainProofs.verifyWithdrawalProofs: Invalid slot merkle proof");

277           require(Merkle.verifyInclusionSha256(proofs.executionPayloadProof, proofs.blockHeaderRoot, proofs.executionPayloadRoot, executionPayloadIndex),
278:              "BeaconChainProofs.verifyWithdrawalProofs: Invalid executionPayload merkle proof");

281           require(Merkle.verifyInclusionSha256(proofs.blockNumberProof, proofs.executionPayloadRoot, proofs.blockNumberRoot, BLOCK_NUMBER_INDEX),
282:              "BeaconChainProofs.verifyWithdrawalProofs: Invalid blockNumber merkle proof");

293           require(Merkle.verifyInclusionSha256(proofs.withdrawalProof, proofs.executionPayloadRoot, withdrawalRoot, withdrawalIndex),
294:              "BeaconChainProofs.verifyWithdrawalProofs: Invalid withdrawal merkle proof");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BeaconChainProofs.sol#L199

File: src/contracts/permissions/Pausable.sol

33:           require(msg.sender == pauserRegistry.pauser(), "msg.sender is not permissioned as pauser");

38:           require(msg.sender == pauserRegistry.unpauser(), "msg.sender is not permissioned as unpauser");

56            require(
57                address(pauserRegistry) == address(0) && address(_pauserRegistry) != address(0),
58                "Pausable._initializePauser: _initializePauser() can only be called once"
59:           );

73:           require((_paused & newPausedStatus) == _paused, "Pausable.pause: invalid attempt to unpause functionality");

94:           require(((~_paused) & (~newPausedStatus)) == (~_paused), "Pausable.unpause: invalid attempt to pause functionality");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L33

File: src/contracts/permissions/PauserRegistry.sol

22:           require(msg.sender == unpauser, "msg.sender is not permissioned as unpauser");

42:           require(newPauser != address(0), "PauserRegistry._setPauser: zero address input");

48:           require(newUnpauser != address(0), "PauserRegistry._setUnpauser: zero address input");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/PauserRegistry.sol#L22

File: src/contracts/pods/DelayedWithdrawalRouter.sol

40:           require(address(eigenPodManager.getPod(podOwner)) == msg.sender, "DelayedWithdrawalRouter.onlyEigenPod: not podOwner's EigenPod");

45:           require(address(_eigenPodManager) != address(0), "DelayedWithdrawalRouter.constructor: _eigenPodManager cannot be zero address");

167:          require(newValue <= MAX_WITHDRAWAL_DELAY_BLOCKS, "DelayedWithdrawalRouter._setWithdrawalDelayBlocks: newValue too large");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L40

File: src/contracts/pods/EigenPodManager.sol

67:           require(address(ownerToPod[podOwner]) == msg.sender, "EigenPodManager.onlyEigenPod: not a pod");

72:           require(msg.sender == address(strategyManager), "EigenPodManager.onlyStrategyManager: not strategyManager");

100:          require(!hasPod(msg.sender), "EigenPodManager.createPod: Sender already has a pod");

217:          require(stateRoot != bytes32(0), "EigenPodManager.getBeaconChainStateRoot: state root at blockNumber not yet finalized");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L67

File: src/contracts/pods/EigenPod.sol

100:          require(msg.sender == address(eigenPodManager), "EigenPod.onlyEigenPodManager: not eigenPodManager");

105:          require(msg.sender == podOwner, "EigenPod.onlyEigenPodOwner: not podOwner");

110:          require(!eigenPodManager.slasher().isFrozen(podOwner), "EigenPod.onlyNotFrozen: pod owner is frozen");

115:          require(!hasRestaked, "EigenPod.hasNeverRestaked: restaking is enabled");

121           require(blockNumber > mostRecentWithdrawalBlockNumber,
122:              "EigenPod.proofIsForValidBlockNumber: beacon chain proof must be for block number after mostRecentWithdrawalBlockNumber");

132:          require(!IPausable(address(eigenPodManager)).paused(index), "EigenPod.onlyWhenNotPaused: index is paused in EigenPodManager");

147:          require(_REQUIRED_BALANCE_WEI % GWEI_TO_WEI == 0, "EigenPod.contructor: _REQUIRED_BALANCE_WEI is not a whole number of gwei");

153:          require(_podOwner != address(0), "EigenPod.initialize: podOwner cannot be zero address");

160:          require(msg.value == 32 ether, "EigenPod.stake: must initially stake for any validator with 32 ether");

186           require(validatorStatus[validatorIndex] == VALIDATOR_STATUS.INACTIVE,
187:              "EigenPod.verifyCorrectWithdrawalCredentials: Validator must be inactive to prove withdrawal credentials");

189           require(validatorFields[BeaconChainProofs.VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX] == bytes32(_podWithdrawalCredentials()),
190:              "EigenPod.verifyCorrectWithdrawalCredentials: Proof is not for this EigenPod");

195           require(validatorCurrentBalanceGwei >= REQUIRED_BALANCE_GWEI,
196:              "EigenPod.verifyCorrectWithdrawalCredentials: ETH validator's balance must be greater than or equal to the restaked balance per validator");

249           require(oracleBlockNumber + VERIFY_OVERCOMMITTED_WINDOW_BLOCKS >= block.number,
250:              "EigenPod.verifyOvercommittedStake: specified blockNumber is too far in past");

252:          require(validatorStatus[validatorIndex] == VALIDATOR_STATUS.ACTIVE, "EigenPod.verifyOvercommittedStake: Validator not active");

257           require(validatorCurrentBalanceGwei < REQUIRED_BALANCE_GWEI,
258:              "EigenPod.verifyOvercommittedStake: validator's balance must be less than the restaked balance per validator");

270:              require(slashedStatus == 1, "EigenPod.verifyOvercommittedStake: Validator must be slashed to be overcommitted");

333           require(validatorStatus[validatorIndex] != VALIDATOR_STATUS.INACTIVE,
334:              "EigenPod.verifyOvercommittedStake: Validator never proven to have withdrawal credentials pointed to this contract");

408:              revert("EigenPod.verifyBeaconChainFullWithdrawal: VALIDATOR_STATUS is WITHDRAWN or invalid VALIDATOR_STATUS");

423:          require(!provenPartialWithdrawal[validatorIndex][withdrawalHappenedSlot], "EigenPod._processPartialWithdrawal: partial withdrawal has already been proven for this slot");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L100

File: src/contracts/strategies/StrategyBase.sol

86:           require(token == underlyingToken, "StrategyBase.deposit: Can only deposit underlyingToken");

104:          require(newShares != 0, "StrategyBase.deposit: newShares cannot be zero");

106           require(updatedTotalShares >= MIN_NONZERO_TOTAL_SHARES,
107:              "StrategyBase.deposit: updated totalShares amount would be nonzero but below MIN_NONZERO_TOTAL_SHARES");

128:          require(token == underlyingToken, "StrategyBase.withdraw: Can only withdraw the strategy token");

131           require(
132               amountShares <= priorTotalShares,
133               "StrategyBase.withdraw: amountShares must be less than or equal to totalShares"
134:          );

139           require(updatedTotalShares >= MIN_NONZERO_TOTAL_SHARES || updatedTotalShares == 0,
140:              "StrategyBase.withdraw: updated totalShares amount would be nonzero but below MIN_NONZERO_TOTAL_SHARES");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L86

[G‑10] Optimize names to save gas

public/external function names and public member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted

There are 19 instances of this issue:

File: src/contracts/core/StrategyManager.sol

/// @audit initialize(), depositBeaconChainETH(), recordOvercommittedBeaconChainETH(), depositIntoStrategy(), depositIntoStrategyWithSignature(), undelegate(), queueWithdrawal(), completeQueuedWithdrawal(), completeQueuedWithdrawals(), slashShares(), slashQueuedWithdrawal(), setWithdrawalDelayBlocks(), setStrategyWhitelister(), addStrategiesToDepositWhitelist(), removeStrategiesFromDepositWhitelist(), getDeposits(), stakerStrategyListLength(), calculateWithdrawalRoot()
26:   contract StrategyManager is

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L26

File: src/contracts/interfaces/IBeaconChainOracle.sol

/// @audit latestConfirmedOracleBlockNumber(), beaconStateRootAtBlockNumber(), isOracleSigner(), hasVoted(), stateRootVotes(), totalOracleSigners(), threshold(), setThreshold(), addOracleSigners(), removeOracleSigners(), voteForBeaconChainStateRoot()
8:    interface IBeaconChainOracle {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IBeaconChainOracle.sol#L8

File: src/contracts/interfaces/IDelayedWithdrawalRouter.sol

/// @audit createDelayedWithdrawal(), claimDelayedWithdrawals(), claimDelayedWithdrawals(), setWithdrawalDelayBlocks(), userWithdrawals(), claimableUserDelayedWithdrawals(), userDelayedWithdrawalByIndex(), userWithdrawalsLength(), canClaimDelayedWithdrawal(), withdrawalDelayBlocks()
4:    interface IDelayedWithdrawalRouter {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IDelayedWithdrawalRouter.sol#L4

File: src/contracts/interfaces/IDelegationManager.sol

/// @audit registerAsOperator(), delegateTo(), delegateToBySignature(), undelegate(), delegatedTo(), delegationTerms(), operatorShares(), increaseDelegatedShares(), decreaseDelegatedShares(), isDelegated(), isNotDelegated(), isOperator()
15:   interface IDelegationManager {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IDelegationManager.sol#L15

File: src/contracts/interfaces/IEigenPodManager.sol

/// @audit createPod(), stake(), restakeBeaconChainETH(), recordOvercommittedBeaconChainETH(), withdrawRestakedBeaconChainETH(), updateBeaconChainOracle(), ownerToPod(), getPod(), beaconChainOracle(), getBeaconChainStateRoot(), strategyManager(), slasher(), hasPod()
14:   interface IEigenPodManager is IPausable {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IEigenPodManager.sol#L14

File: src/contracts/interfaces/IEigenPod.sol

/// @audit REQUIRED_BALANCE_GWEI(), REQUIRED_BALANCE_WEI(), validatorStatus(), restakedExecutionLayerGwei(), initialize(), stake(), withdrawRestakedBeaconChainETH(), eigenPodManager(), podOwner(), hasRestaked(), mostRecentWithdrawalBlockNumber(), provenPartialWithdrawal(), verifyWithdrawalCredentialsAndBalance(), verifyOvercommittedStake(), verifyAndProcessWithdrawal(), withdrawBeforeRestaking()
21:   interface IEigenPod {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IEigenPod.sol#L21

File: src/contracts/interfaces/IETHPOSDeposit.sol

/// @audit deposit(), get_deposit_root(), get_deposit_count()
17:   interface IETHPOSDeposit {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IETHPOSDeposit.sol#L17

File: src/contracts/interfaces/IPausable.sol

/// @audit pauserRegistry(), pause(), pauseAll(), unpause(), paused()
10:   interface IPausable {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IPausable.sol#L10

File: src/contracts/interfaces/IPauserRegistry.sol

/// @audit pauser(), unpauser()
8:    interface IPauserRegistry {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IPauserRegistry.sol#L8

File: src/contracts/interfaces/IServiceManager.sol

/// @audit taskNumber(), freezeOperator(), recordFirstStakeUpdate(), recordStakeUpdate(), recordLastStakeUpdateAndRevokeSlashingAbility(), latestServeUntilBlock()
11:   interface IServiceManager {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IServiceManager.sol#L11

File: src/contracts/interfaces/ISlasher.sol

/// @audit optIntoSlashing(), freezeOperator(), resetFrozenStatus(), recordFirstStakeUpdate(), recordStakeUpdate(), recordLastStakeUpdateAndRevokeSlashingAbility(), isFrozen(), canSlash(), contractCanSlashOperatorUntilBlock(), latestUpdateBlock(), getCorrectValueForInsertAfter(), canWithdraw(), operatorToMiddlewareTimes(), middlewareTimesLength(), getMiddlewareTimesIndexBlock(), getMiddlewareTimesIndexServeUntilBlock(), operatorWhitelistedContractsLinkedListSize(), operatorWhitelistedContractsLinkedListEntry()
9:    interface ISlasher {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/ISlasher.sol#L9

File: src/contracts/interfaces/IStrategyManager.sol

/// @audit depositIntoStrategy(), depositBeaconChainETH(), recordOvercommittedBeaconChainETH(), depositIntoStrategyWithSignature(), stakerStrategyShares(), getDeposits(), stakerStrategyListLength(), queueWithdrawal(), completeQueuedWithdrawal(), completeQueuedWithdrawals(), slashShares(), slashQueuedWithdrawal(), calculateWithdrawalRoot(), addStrategiesToDepositWhitelist(), removeStrategiesFromDepositWhitelist(), delegation(), slasher(), beaconChainETHStrategy(), withdrawalDelayBlocks()
13:   interface IStrategyManager {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IStrategyManager.sol#L13

File: src/contracts/interfaces/IStrategy.sol

/// @audit deposit(), withdraw(), sharesToUnderlying(), underlyingToShares(), userUnderlying(), sharesToUnderlyingView(), underlyingToSharesView(), userUnderlyingView(), underlyingToken(), totalShares(), explanation()
11:   interface IStrategy {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/interfaces/IStrategy.sol#L11

File: src/contracts/permissions/Pausable.sol

/// @audit pause(), pauseAll(), unpause(), paused()
15:   contract Pausable is IPausable {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L15

File: src/contracts/permissions/PauserRegistry.sol

/// @audit setPauser(), setUnpauser()
10:   contract PauserRegistry is IPauserRegistry {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/PauserRegistry.sol#L10

File: src/contracts/pods/DelayedWithdrawalRouter.sol

/// @audit initialize(), createDelayedWithdrawal(), claimDelayedWithdrawals(), claimDelayedWithdrawals(), setWithdrawalDelayBlocks(), userWithdrawals(), claimableUserDelayedWithdrawals(), userDelayedWithdrawalByIndex(), userWithdrawalsLength(), canClaimDelayedWithdrawal()
11:   contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable, Pausable, IDelayedWithdrawalRouter {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L11

File: src/contracts/pods/EigenPodManager.sol

/// @audit initialize(), createPod(), stake(), restakeBeaconChainETH(), recordOvercommittedBeaconChainETH(), withdrawRestakedBeaconChainETH(), updateBeaconChainOracle(), getPod(), hasPod(), getBeaconChainStateRoot()
31:   contract EigenPodManager is Initializable, OwnableUpgradeable, Pausable, IEigenPodManager, EigenPodPausingConstants {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L31

File: src/contracts/pods/EigenPod.sol

/// @audit initialize(), stake(), verifyWithdrawalCredentialsAndBalance(), verifyOvercommittedStake(), verifyAndProcessWithdrawal(), withdrawRestakedBeaconChainETH(), withdrawBeforeRestaking()
34:   contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L34

File: src/contracts/strategies/StrategyBase.sol

/// @audit initialize(), underlyingToSharesView(), underlyingToShares(), userUnderlyingView(), userUnderlying(), shares()
19:   contract StrategyBase is Initializable, Pausable, IStrategy {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L19

[G‑11] Using bools for storage incurs overhead

    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27 Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from false to true, after having been true in the past

There are 4 instances of this issue:

File: src/contracts/core/StrategyManagerStorage.sol

53:       mapping(bytes32 => bool) public withdrawalRootPending;

57:       mapping(IStrategy => bool) public strategyIsWhitelistedForDeposit;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManagerStorage.sol#L53

File: src/contracts/pods/EigenPod.sol

73:       bool public hasRestaked;

79:       mapping(uint40 => mapping(uint64 => bool)) public provenPartialWithdrawal;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L73

[G‑12] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)

Saves 5 gas per loop

There are 4 instances of this issue:

File: src/contracts/core/StrategyManager.sol

466:          for(uint256 i = 0; i < queuedWithdrawals.length; i++) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L466

File: src/contracts/libraries/Merkle.sol

137:          for (uint i = 0; i < numNodesInLayer; i++) {

145:              for (uint i = 0; i < numNodesInLayer; i++) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/Merkle.sol#L137

File: src/contracts/pods/DelayedWithdrawalRouter.sol

115:          for (uint256 i = 0; i < claimableDelayedWithdrawalsLength; i++) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L115

[G‑13] Splitting require() statements that use && saves gas

See this issue which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper by 3 gas

There is one instance of this issue:

File: src/contracts/permissions/Pausable.sol

56            require(
57                address(pauserRegistry) == address(0) && address(_pauserRegistry) != address(0),
58                "Pausable._initializePauser: _initializePauser() can only be called once"
59:           );

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L56-L59

[G‑14] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed

There are 4 instances of this issue:

File: src/contracts/libraries/Endian.sol

/// @audit uint64 n
9:            n = uint64(uint256(lenum >> 192));

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/Endian.sol#L9

File: src/contracts/pods/EigenPod.sol

/// @audit uint64 REQUIRED_BALANCE_GWEI
146:          REQUIRED_BALANCE_GWEI = uint64(_REQUIRED_BALANCE_WEI / GWEI_TO_WEI);

/// @audit uint64 restakedExecutionLayerGwei
445:          restakedExecutionLayerGwei -= uint64(amountWei / GWEI_TO_WEI);

/// @audit uint64 mostRecentWithdrawalBlockNumber
455:          mostRecentWithdrawalBlockNumber = uint32(block.number);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L146

[G‑15] Using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table

There are 6 instances of this issue:

File: src/contracts/core/StrategyManagerStorage.sol

17        bytes32 public constant DOMAIN_TYPEHASH =
18:           keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");

20        bytes32 public constant DEPOSIT_TYPEHASH =
21:           keccak256("Deposit(address strategy,address token,uint256 amount,uint256 nonce,uint256 expiry)");

46:       uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 50400;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManagerStorage.sol#L17-L18

File: src/contracts/pods/DelayedWithdrawalRouter.sol

24:       uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 50400;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L24

File: src/contracts/pods/EigenPod.sol

53:       uint64 public immutable REQUIRED_BALANCE_GWEI;

56:       uint256 public immutable REQUIRED_BALANCE_WEI;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L53

[G‑16] Division by two should use bit shifting

<x> / 2 is the same as <x> >> 1. While the compiler uses the SHR opcode to accomplish both, the version that uses division incurs an overhead of 20 gas due to JUMPs to and from a compiler utility function that introduces checks which can be avoided by using unchecked {} around the division by two

There are 2 instances of this issue:

File: src/contracts/libraries/BeaconChainProofs.sol

233:          uint256 balanceIndex = uint256(validatorIndex/4);

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BeaconChainProofs.sol#L233

File: src/contracts/libraries/Merkle.sol

133:          uint256 numNodesInLayer = leaves.length / 2;

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/Merkle.sol#L133

[G‑17] require() or revert() statements that check input arguments should be at the top of the function

Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas*) in a function that may ultimately revert in the unhappy case.

There is one instance of this issue:

File: src/contracts/core/StrategyManager.sol

/// @audit expensive op on line 342
343:          require(withdrawer != address(0), "StrategyManager.queueWithdrawal: cannot withdraw to zero address");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L343

[G‑18] Use custom errors rather than revert()/require() strings to save gas

Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas

There are 88 instances of this issue:

File: src/contracts/core/StrategyManager.sol

97            require(
98                !slasher.isFrozen(staker),
99                "StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing"
100:          );

105:          require(slasher.isFrozen(staker), "StrategyManager.onlyFrozen: staker has not been frozen");

110:          require(address(eigenPodManager) == msg.sender, "StrategyManager.onlyEigenPodManager: not the eigenPodManager");

115:          require(msg.sender == strategyWhitelister, "StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister");

120:          require(strategyIsWhitelistedForDeposit[strategy], "StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted");

262           require(
263               expiry >= block.timestamp,
264               "StrategyManager.depositIntoStrategyWithSignature: signature expired"
265:          );

290               require(IERC1271(staker).isValidSignature(digestHash, signature) == ERC1271_MAGICVALUE,
291:                  "StrategyManager.depositIntoStrategyWithSignature: ERC1271 signature verification failed");

293               require(ECDSA.recover(digestHash, signature) == staker,
294:                  "StrategyManager.depositIntoStrategyWithSignature: signature not from staker");

342:          require(strategies.length == shares.length, "StrategyManager.queueWithdrawal: input length mismatch");

343:          require(withdrawer != address(0), "StrategyManager.queueWithdrawal: cannot withdraw to zero address");

360                   require(withdrawer == msg.sender,
361:                      "StrategyManager.queueWithdrawal: cannot queue a withdrawal of Beacon Chain ETH to a different address");

362                   require(strategies.length == 1,
363:                      "StrategyManager.queueWithdrawal: cannot queue a withdrawal including Beacon Chain ETH and other tokens");

364                   require(shares[i] % GWEI_TO_WEI == 0,
365:                      "StrategyManager.queueWithdrawal: cannot queue a withdrawal of Beacon Chain ETH for an non-whole amount of gwei");

495:          require(tokens.length == strategies.length, "StrategyManager.slashShares: input length mismatch");

542:          require(tokens.length == queuedWithdrawal.strategies.length, "StrategyManager.slashQueuedWithdrawal: input length mismatch");

548           require(
549               withdrawalRootPending[withdrawalRoot],
550               "StrategyManager.slashQueuedWithdrawal: withdrawal is not pending"
551:          );

631:          require(depositor != address(0), "StrategyManager._addShares: depositor cannot be zero address");

632:          require(shares != 0, "StrategyManager._addShares: shares should not be zero!");

636               require(
637                   stakerStrategyList[depositor].length < MAX_STAKER_STRATEGY_LIST_LENGTH,
638                   "StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH"
639:              );

684:          require(depositor != address(0), "StrategyManager._removeShares: depositor cannot be zero address");

685:          require(shareAmount != 0, "StrategyManager._removeShares: shareAmount should not be zero!");

690:          require(shareAmount <= userShares, "StrategyManager._removeShares: shareAmount too high");

736:              require(j != stratsLength, "StrategyManager._removeStrategyFromStakerStrategyList: strategy not found");

750           require(
751               withdrawalRootPending[withdrawalRoot],
752               "StrategyManager.completeQueuedWithdrawal: withdrawal is not pending"
753:          );

755           require(
756               slasher.canWithdraw(queuedWithdrawal.delegatedAddress, queuedWithdrawal.withdrawalStartBlock, middlewareTimesIndex),
757               "StrategyManager.completeQueuedWithdrawal: shares pending withdrawal are still slashable"
758:          );

761           require(queuedWithdrawal.withdrawalStartBlock + withdrawalDelayBlocks <= block.number 
762                   || queuedWithdrawal.strategies[0] == beaconChainETHStrategy,
763               "StrategyManager.completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed"
764:          );

766           require(
767               msg.sender == queuedWithdrawal.withdrawerAndNonce.withdrawer,
768               "StrategyManager.completeQueuedWithdrawal: only specified withdrawer can complete a queued withdrawal"
769:          );

778:              require(tokens.length == queuedWithdrawal.strategies.length, "StrategyManager.completeQueuedWithdrawal: input length mismatch");

812:          require(stakerStrategyList[depositor].length == 0, "StrategyManager._undelegate: depositor has active deposits");

840:          require(_withdrawalDelayBlocks <= MAX_WITHDRAWAL_DELAY_BLOCKS, "StrategyManager.setWithdrawalDelay: _withdrawalDelayBlocks too high");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L97-L100

File: src/contracts/libraries/BeaconChainProofs.sol

199:          require(validatorFields.length == 2**VALIDATOR_FIELD_TREE_HEIGHT, "BeaconChainProofs.verifyValidatorFields: Validator fields has incorrect length");

205:          require(proof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorFields: Proof has incorrect length");

211:          require(Merkle.verifyInclusionSha256(proof, beaconStateRoot, validatorRoot, index), "BeaconChainProofs.verifyValidatorFields: Invalid merkle proof");

227:          require(proof.length == 32 * ((BALANCE_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorBalance: Proof has incorrect length");

236:          require(Merkle.verifyInclusionSha256(proof, beaconStateRoot, balanceRoot, balanceIndex), "BeaconChainProofs.verifyValidatorBalance: Invalid merkle proof");

250:          require(withdrawalFields.length == 2**WITHDRAWAL_FIELD_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: withdrawalFields has incorrect length");

252:          require(proofs.blockHeaderRootIndex < 2**BLOCK_ROOTS_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: blockRootIndex is too large");

253:          require(proofs.withdrawalIndex < 2**WITHDRAWALS_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: withdrawalIndex is too large");

256           require(proofs.blockHeaderProof.length == 32 * (BEACON_STATE_FIELD_TREE_HEIGHT + BLOCK_ROOTS_TREE_HEIGHT),
257:              "BeaconChainProofs.verifyWithdrawalProofs: blockHeaderProof has incorrect length");

258           require(proofs.withdrawalProof.length == 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT + WITHDRAWALS_TREE_HEIGHT + 1),
259:              "BeaconChainProofs.verifyWithdrawalProofs: withdrawalProof has incorrect length");

260           require(proofs.executionPayloadProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT),
261:              "BeaconChainProofs.verifyWithdrawalProofs: executionPayloadProof has incorrect length");

269           require(Merkle.verifyInclusionSha256(proofs.blockHeaderProof, beaconStateRoot, proofs.blockHeaderRoot, blockHeaderIndex),
270:              "BeaconChainProofs.verifyWithdrawalProofs: Invalid block header merkle proof");

273:          require(Merkle.verifyInclusionSha256(proofs.slotProof, proofs.blockHeaderRoot, proofs.slotRoot, SLOT_INDEX), "BeaconChainProofs.verifyWithdrawalProofs: Invalid slot merkle proof");

277           require(Merkle.verifyInclusionSha256(proofs.executionPayloadProof, proofs.blockHeaderRoot, proofs.executionPayloadRoot, executionPayloadIndex),
278:              "BeaconChainProofs.verifyWithdrawalProofs: Invalid executionPayload merkle proof");

281           require(Merkle.verifyInclusionSha256(proofs.blockNumberProof, proofs.executionPayloadRoot, proofs.blockNumberRoot, BLOCK_NUMBER_INDEX),
282:              "BeaconChainProofs.verifyWithdrawalProofs: Invalid blockNumber merkle proof");

293           require(Merkle.verifyInclusionSha256(proofs.withdrawalProof, proofs.executionPayloadRoot, withdrawalRoot, withdrawalIndex),
294:              "BeaconChainProofs.verifyWithdrawalProofs: Invalid withdrawal merkle proof");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BeaconChainProofs.sol#L199

File: src/contracts/permissions/Pausable.sol

33:           require(msg.sender == pauserRegistry.pauser(), "msg.sender is not permissioned as pauser");

38:           require(msg.sender == pauserRegistry.unpauser(), "msg.sender is not permissioned as unpauser");

44:           require(_paused == 0, "Pausable: contract is paused");

50:           require(!paused(index), "Pausable: index is paused");

56            require(
57                address(pauserRegistry) == address(0) && address(_pauserRegistry) != address(0),
58                "Pausable._initializePauser: _initializePauser() can only be called once"
59:           );

73:           require((_paused & newPausedStatus) == _paused, "Pausable.pause: invalid attempt to unpause functionality");

94:           require(((~_paused) & (~newPausedStatus)) == (~_paused), "Pausable.unpause: invalid attempt to pause functionality");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L33

File: src/contracts/permissions/PauserRegistry.sol

22:           require(msg.sender == unpauser, "msg.sender is not permissioned as unpauser");

42:           require(newPauser != address(0), "PauserRegistry._setPauser: zero address input");

48:           require(newUnpauser != address(0), "PauserRegistry._setUnpauser: zero address input");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/PauserRegistry.sol#L22

File: src/contracts/pods/DelayedWithdrawalRouter.sol

40:           require(address(eigenPodManager.getPod(podOwner)) == msg.sender, "DelayedWithdrawalRouter.onlyEigenPod: not podOwner's EigenPod");

45:           require(address(_eigenPodManager) != address(0), "DelayedWithdrawalRouter.constructor: _eigenPodManager cannot be zero address");

167:          require(newValue <= MAX_WITHDRAWAL_DELAY_BLOCKS, "DelayedWithdrawalRouter._setWithdrawalDelayBlocks: newValue too large");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L40

File: src/contracts/pods/EigenPodManager.sol

67:           require(address(ownerToPod[podOwner]) == msg.sender, "EigenPodManager.onlyEigenPod: not a pod");

72:           require(msg.sender == address(strategyManager), "EigenPodManager.onlyStrategyManager: not strategyManager");

100:          require(!hasPod(msg.sender), "EigenPodManager.createPod: Sender already has a pod");

217:          require(stateRoot != bytes32(0), "EigenPodManager.getBeaconChainStateRoot: state root at blockNumber not yet finalized");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L67

File: src/contracts/pods/EigenPod.sol

100:          require(msg.sender == address(eigenPodManager), "EigenPod.onlyEigenPodManager: not eigenPodManager");

105:          require(msg.sender == podOwner, "EigenPod.onlyEigenPodOwner: not podOwner");

110:          require(!eigenPodManager.slasher().isFrozen(podOwner), "EigenPod.onlyNotFrozen: pod owner is frozen");

115:          require(!hasRestaked, "EigenPod.hasNeverRestaked: restaking is enabled");

121           require(blockNumber > mostRecentWithdrawalBlockNumber,
122:              "EigenPod.proofIsForValidBlockNumber: beacon chain proof must be for block number after mostRecentWithdrawalBlockNumber");

132:          require(!IPausable(address(eigenPodManager)).paused(index), "EigenPod.onlyWhenNotPaused: index is paused in EigenPodManager");

147:          require(_REQUIRED_BALANCE_WEI % GWEI_TO_WEI == 0, "EigenPod.contructor: _REQUIRED_BALANCE_WEI is not a whole number of gwei");

153:          require(_podOwner != address(0), "EigenPod.initialize: podOwner cannot be zero address");

160:          require(msg.value == 32 ether, "EigenPod.stake: must initially stake for any validator with 32 ether");

186           require(validatorStatus[validatorIndex] == VALIDATOR_STATUS.INACTIVE,
187:              "EigenPod.verifyCorrectWithdrawalCredentials: Validator must be inactive to prove withdrawal credentials");

189           require(validatorFields[BeaconChainProofs.VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX] == bytes32(_podWithdrawalCredentials()),
190:              "EigenPod.verifyCorrectWithdrawalCredentials: Proof is not for this EigenPod");

195           require(validatorCurrentBalanceGwei >= REQUIRED_BALANCE_GWEI,
196:              "EigenPod.verifyCorrectWithdrawalCredentials: ETH validator's balance must be greater than or equal to the restaked balance per validator");

249           require(oracleBlockNumber + VERIFY_OVERCOMMITTED_WINDOW_BLOCKS >= block.number,
250:              "EigenPod.verifyOvercommittedStake: specified blockNumber is too far in past");

252:          require(validatorStatus[validatorIndex] == VALIDATOR_STATUS.ACTIVE, "EigenPod.verifyOvercommittedStake: Validator not active");

257           require(validatorCurrentBalanceGwei < REQUIRED_BALANCE_GWEI,
258:              "EigenPod.verifyOvercommittedStake: validator's balance must be less than the restaked balance per validator");

270:              require(slashedStatus == 1, "EigenPod.verifyOvercommittedStake: Validator must be slashed to be overcommitted");

333           require(validatorStatus[validatorIndex] != VALIDATOR_STATUS.INACTIVE,
334:              "EigenPod.verifyOvercommittedStake: Validator never proven to have withdrawal credentials pointed to this contract");

423:          require(!provenPartialWithdrawal[validatorIndex][withdrawalHappenedSlot], "EigenPod._processPartialWithdrawal: partial withdrawal has already been proven for this slot");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L100

File: src/contracts/strategies/StrategyBase.sol

41:           require(msg.sender == address(strategyManager), "StrategyBase.onlyStrategyManager");

86:           require(token == underlyingToken, "StrategyBase.deposit: Can only deposit underlyingToken");

104:          require(newShares != 0, "StrategyBase.deposit: newShares cannot be zero");

106           require(updatedTotalShares >= MIN_NONZERO_TOTAL_SHARES,
107:              "StrategyBase.deposit: updated totalShares amount would be nonzero but below MIN_NONZERO_TOTAL_SHARES");

128:          require(token == underlyingToken, "StrategyBase.withdraw: Can only withdraw the strategy token");

131           require(
132               amountShares <= priorTotalShares,
133               "StrategyBase.withdraw: amountShares must be less than or equal to totalShares"
134:          );

139           require(updatedTotalShares >= MIN_NONZERO_TOTAL_SHARES || updatedTotalShares == 0,
140:              "StrategyBase.withdraw: updated totalShares amount would be nonzero but below MIN_NONZERO_TOTAL_SHARES");

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L41

[G‑19] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 36 instances of this issue:

File: src/contracts/core/StrategyManager.sol

164       function depositBeaconChainETH(address staker, uint256 amount)
165           external
166           onlyEigenPodManager
167           onlyWhenNotPaused(PAUSED_DEPOSITS)
168           onlyNotFrozen(staker)
169:          nonReentrant

182       function recordOvercommittedBeaconChainETH(address overcommittedPodOwner, uint256 beaconChainETHStrategyIndex, uint256 amount)
183           external
184           onlyEigenPodManager
185:          nonReentrant

220       function depositIntoStrategy(IStrategy strategy, IERC20 token, uint256 amount)
221           external
222           onlyWhenNotPaused(PAUSED_DEPOSITS)
223           onlyNotFrozen(msg.sender)
224           nonReentrant
225:          returns (uint256 shares)

248       function depositIntoStrategyWithSignature(
249           IStrategy strategy,
250           IERC20 token,
251           uint256 amount,
252           address staker,
253           uint256 expiry,
254           bytes memory signature
255       )
256           external
257           onlyWhenNotPaused(PAUSED_DEPOSITS)
258           onlyNotFrozen(staker)
259           nonReentrant
260:          returns (uint256 shares)

329       function queueWithdrawal(
330           uint256[] calldata strategyIndexes,
331           IStrategy[] calldata strategies,
332           uint256[] calldata shares,
333           address withdrawer,
334           bool undelegateIfPossible
335       )
336           external
337           onlyWhenNotPaused(PAUSED_WITHDRAWALS)
338           onlyNotFrozen(msg.sender)
339           nonReentrant
340:          returns (bytes32)

442       function completeQueuedWithdrawal(QueuedWithdrawal calldata queuedWithdrawal, IERC20[] calldata tokens, uint256 middlewareTimesIndex, bool receiveAsTokens)
443           external
444           onlyWhenNotPaused(PAUSED_WITHDRAWALS)
445           // check that the address that the staker *was delegated to* – at the time that they queued the withdrawal – is not frozen
446:          nonReentrant

456       function completeQueuedWithdrawals(
457           QueuedWithdrawal[] calldata queuedWithdrawals,
458           IERC20[][] calldata tokens,
459           uint256[] calldata middlewareTimesIndexes,
460           bool[] calldata receiveAsTokens
461       ) external
462           onlyWhenNotPaused(PAUSED_WITHDRAWALS)
463           // check that the address that the staker *was delegated to* – at the time that they queued the withdrawal – is not frozen
464:          nonReentrant

482       function slashShares(
483           address slashedAddress,
484           address recipient,
485           IStrategy[] calldata strategies,
486           IERC20[] calldata tokens,
487           uint256[] calldata strategyIndexes,
488           uint256[] calldata shareAmounts
489       )
490           external
491           onlyOwner
492           onlyFrozen(slashedAddress)
493:          nonReentrant

536       function slashQueuedWithdrawal(address recipient, QueuedWithdrawal calldata queuedWithdrawal, IERC20[] calldata tokens, uint256[] calldata indicesToSkip)
537           external
538           onlyOwner
539           onlyFrozen(queuedWithdrawal.delegatedAddress)
540:          nonReentrant

582:      function setWithdrawalDelayBlocks(uint256 _withdrawalDelayBlocks) external onlyOwner {

587:      function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner {

592:      function addStrategiesToDepositWhitelist(IStrategy[] calldata strategiesToWhitelist) external onlyStrategyWhitelister {

607:      function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external onlyStrategyWhitelister {

655       function _depositIntoStrategy(address depositor, IStrategy strategy, IERC20 token, uint256 amount)
656           internal
657           onlyStrategiesWhitelistedForDeposit(strategy)
658:          returns (uint256 shares)

745:      function _completeQueuedWithdrawal(QueuedWithdrawal calldata queuedWithdrawal, IERC20[] calldata tokens, uint256 middlewareTimesIndex, bool receiveAsTokens) onlyNotFrozen(queuedWithdrawal.delegatedAddress) internal {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L164-L169

File: src/contracts/permissions/Pausable.sol

71:       function pause(uint256 newPausedStatus) external onlyPauser {

81:       function pauseAll() external onlyPauser {

92:       function unpause(uint256 newPausedStatus) external onlyUnpauser {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/Pausable.sol#L71

File: src/contracts/permissions/PauserRegistry.sol

32:       function setPauser(address newPauser) external onlyUnpauser {

37:       function setUnpauser(address newUnpauser) external onlyUnpauser {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/PauserRegistry.sol#L32

File: src/contracts/pods/DelayedWithdrawalRouter.sol

79        function claimDelayedWithdrawals(address recipient, uint256 maxNumberOfDelayedWithdrawalsToClaim)
80            external
81            nonReentrant
82:           onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS)

91        function claimDelayedWithdrawals(uint256 maxNumberOfDelayedWithdrawalsToClaim)
92            external
93            nonReentrant
94:           onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS)

100:      function setWithdrawalDelayBlocks(uint256 newValue) external onlyOwner {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L79-L82

File: src/contracts/pods/EigenPodManager.sol

127:      function restakeBeaconChainETH(address podOwner, uint256 amount) external onlyEigenPod(podOwner) {

139:      function recordOvercommittedBeaconChainETH(address podOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) external onlyEigenPod(podOwner) {

150       function withdrawRestakedBeaconChainETH(address podOwner, address recipient, uint256 amount)
151:          external onlyStrategyManager onlyWhenNotPaused(PAUSED_WITHDRAW_RESTAKED_ETH)

161:      function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external onlyOwner {

166:      function _deployPod() internal onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (IEigenPod) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L127

File: src/contracts/pods/EigenPod.sol

175       function verifyWithdrawalCredentialsAndBalance(
176           uint64 oracleBlockNumber,
177           uint40 validatorIndex,
178           BeaconChainProofs.ValidatorFieldsAndBalanceProofs calldata proofs,
179           bytes32[] calldata validatorFields
180       )
181           external
182           onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
183           // check that the provided `oracleBlockNumber` is after the `mostRecentWithdrawalBlockNumber`
184:          proofIsForValidBlockNumber(oracleBlockNumber)

241       function verifyOvercommittedStake(
242           uint40 validatorIndex,
243           BeaconChainProofs.ValidatorFieldsAndBalanceProofs calldata proofs,
244           bytes32[] calldata validatorFields,
245           uint256 beaconChainETHStrategyIndex,
246           uint64 oracleBlockNumber
247:      ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_OVERCOMMITTED) {

305       function verifyAndProcessWithdrawal(
306           BeaconChainProofs.WithdrawalProofs calldata withdrawalProofs, 
307           bytes calldata validatorFieldsProof,
308           bytes32[] calldata validatorFields,
309           bytes32[] calldata withdrawalFields,
310           uint256 beaconChainETHStrategyIndex,
311           uint64 oracleBlockNumber
312       )
313           external
314           onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_WITHDRAWAL)
315           onlyNotFrozen
316           /** 
317            * Check that the provided block number being proven against is after the `mostRecentWithdrawalBlockNumber`.
318            * Without this check, there is an edge case where a user proves a past withdrawal for a validator whose funds they already withdrew,
319            * as a way to "withdraw the same funds twice" without providing adequate proof.
320            * Note that this check is not made using the oracleBlockNumber as in the `verifyWithdrawalCredentials` proof; instead this proof
321            * proof is made for the block number of the withdrawal, which may be within 8192 slots of the oracleBlockNumber. 
322            * This difference in modifier usage is OK, since it is still not possible to `verifyAndProcessWithdrawal` against a slot that occurred
323            * *prior* to the proof provided in the `verifyWithdrawalCredentials` function.
324            */
325:          proofIsForValidBlockNumber(Endian.fromLittleEndianUint64(withdrawalProofs.blockNumberRoot))

437       function withdrawRestakedBeaconChainETH(
438           address recipient,
439           uint256 amountWei
440       )
441           external
442:          onlyEigenPodManager

454:      function withdrawBeforeRestaking() external onlyEigenPodOwner hasNeverRestaked {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L175-L184

File: src/contracts/strategies/StrategyBase.sol

56:       function _initializeStrategyBase(IERC20 _underlyingToken, IPauserRegistry _pauserRegistry) internal onlyInitializing {

78        function deposit(IERC20 token, uint256 amount)
79            external
80            virtual
81            override
82            onlyWhenNotPaused(PAUSED_DEPOSITS)
83            onlyStrategyManager
84:           returns (uint256 newShares)

121       function withdraw(address depositor, IERC20 token, uint256 amountShares)
122           external
123           virtual
124           override
125           onlyWhenNotPaused(PAUSED_WITHDRAWALS)
126:          onlyStrategyManager

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L56

[G‑20] Constructors can be marked payable

Payable functions cost less gas to execute, since the compiler does not have to add extra checks to ensure that a payment wasn't provided. A constructor can safely be marked as payable, since only the deployer would be able to pass funds, and the project itself would not pass any funds.

There are 7 instances of this issue:

File: src/contracts/core/StrategyManager.sol

129       constructor(IDelegationManager _delegation, IEigenPodManager _eigenPodManager, ISlasher _slasher)
130:          StrategyManagerStorage(_delegation, _eigenPodManager, _slasher)

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManager.sol#L129-L130

File: src/contracts/core/StrategyManagerStorage.sol

72:       constructor(IDelegationManager _delegation, IEigenPodManager _eigenPodManager, ISlasher _slasher) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/core/StrategyManagerStorage.sol#L72

File: src/contracts/permissions/PauserRegistry.sol

26:       constructor(address _pauser, address _unpauser) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/permissions/PauserRegistry.sol#L26

File: src/contracts/pods/DelayedWithdrawalRouter.sol

44:       constructor(IEigenPodManager _eigenPodManager) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/DelayedWithdrawalRouter.sol#L44

File: src/contracts/pods/EigenPodManager.sol

76:       constructor(IETHPOSDeposit _ethPOS, IBeacon _eigenPodBeacon, IStrategyManager _strategyManager, ISlasher _slasher) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPodManager.sol#L76

File: src/contracts/pods/EigenPod.sol

136       constructor(
137           IETHPOSDeposit _ethPOS,
138           IDelayedWithdrawalRouter _delayedWithdrawalRouter,
139           IEigenPodManager _eigenPodManager,
140:          uint256 _REQUIRED_BALANCE_WEI

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/pods/EigenPod.sol#L136-L140

File: src/contracts/strategies/StrategyBase.sol

46:       constructor(IStrategyManager _strategyManager) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/strategies/StrategyBase.sol#L46

@CloudEllie
Copy link
Author

Judge comments

Judge: @GalloDaSballo

[M‑01] Contracts are vulnerable to fee-on-transfer-token-related accounting issues 1
L

[L‑01] Events are missing sender information 6
R

[L‑02] The owner is a single point of failure and a centralization risk 27
M

[L‑03] Use Ownable2Step's transfer function rather than Ownable's for transfers of ownership 2
R

[L‑04] Unsafe downcast 3
L

[L‑05] Loss of precision 4
L

[L‑06] Upgradeable contract not initialized 6
L

[L‑07] Array lengths not checked 3
R

[L‑08] Missing checks for address(0x0) when assigning values to address state variables 1
L

[L‑09] abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256() 2
Disagree

[L‑10] Use Ownable2Step rather than Ownable 3
R

[N‑01] Constants in comparisons should appear on the left side 118
NC

[N‑02] Variables need not be initialized to zero 15
R

[N‑03] Imports could be organized more systematically 13
R

[N‑04] Large numeric literals should use underscores for readability 14
R

[N‑05] Upgradeable contract is missing a __gap[50] storage variable to allow for new storage variables in later versions L

[N‑06] Import declarations should import specific identifiers, rather than the whole file 71
NC

[N‑07] Missing initializer modifier on constructor 1
R

[N‑08] Unused file 1
R

[N‑09] The nonReentrant modifier should occur before all other modifiers 9
R

[N‑10] Adding a return statement when the function defines a named return variable, is redundant 2
R

[N‑11] public functions not called by the contract should be declared external instead 1
R

[N‑12] constants should be defined rather than using magic numbers 56
R

[N‑13] Cast is more restrictive than the type of the variable being assigned 1
Invalid (block time looks fine to me)

[N‑14] Numeric values having to do with time should use time units for readability 1
Disagree

[N‑15] Use a more recent version of solidity 3
R

[N‑16] Expressions for constant values such as a call to keccak256(), should use immutable rather than constant 2
Disagree

[N‑17] Constant redefined elsewhere 6
R

[N‑18] Inconsistent spacing in comments 24
NC

[N‑19] Lines are too long 23
NC

[N‑20] Variable names that consist of all capital letters should be reserved for constant/immutable variables 2
NC

[N‑21] Typos 21
NC

[N‑22] Misplaced SPDX identifier 1
NC

[N‑23] File is missing NatSpec 1
NC

[N‑24] NatSpec is incomplete 41
NC

[N‑25] Event is missing indexed fields 23
Disagree

[N‑26] Consider using delete rather than assigning zero to clear values 1
NC

[N‑27] Avoid the use of sensitive terms 31
NC

[N‑28] Contracts should have full test coverage 1
Ignoring
[N‑29] Large or complicated code bases should implement invariant tests 1
Ignoring

[N‑30] Function ordering does not follow the Solidity style guide 7
NC

[N‑31] Contract does not follow the Solidity style guide's suggested layout ordering 7
NC

Gas Optimizations
Issue Instances Total Gas Saved
[G‑01] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate 1 -
NC

[G‑02] State variables should be cached in stack variables rather than re-reading them from storage 15 1455
L

[G‑03] Multiple accesses of a mapping/array should use a local variable cache 11 462
NC

[G‑04] += costs more gas than = + for state variables 5 565
NC

[G‑05] internal functions only called once can be inlined to save gas 4 80
Ignoring

[G‑06] Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement 5 425
NC

[G‑07] .length should not be looked up in every loop of a for-loop 4 12
NC

[G‑08] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops 8 480
NC

[G‑09] require()/revert() strings longer than 32 bytes cost extra gas 86 -
Ignoring

[G‑10] Optimize names to save gas 19 418
Ignoring

[G‑11] Using bools for storage incurs overhead 4 68400
Ignoring

[G‑12] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too) 4 20
NC

[G‑13] Splitting require() statements that use && saves gas 1 3
Ignoring

[G‑14] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead 4 -
Ignoring

[G‑15] Using private rather than public for constants, saves gas 6 -
Ignoring

[G‑16] Division by two should use bit shifting 2 40
NC

[G‑17] require() or revert() statements that check input arguments should be at the top of the function 1 -
Ignoring

[G‑18] Use custom errors rather than revert()/require() strings to save gas 88 -
Ignoring

[G‑19] Functions guaranteed to revert when called by normal users can be marked payable 36 756
Ignoring

[G‑20] Constructors can be marked payable 7 147
Ignoring


  • 1M
  • 7 L
  • 15 R
  • 21 NC
  • 13 Ignored
  • 4 Disagree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment