Issue | Instances | |
---|---|---|
[M‑01] | Contracts are vulnerable to fee-on-transfer-token-related accounting issues | 1 |
Total: 1 instances over 1 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
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] | constant s 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
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 bool s 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.
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);
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);
File: src/contracts/middleware/example/HashThreshold.sol
92: emit MessageCertified(message);
File: script/M1_Deploy.s.sol
93: emit log_named_uint("You are deploying on ChainID", chainId);
File: src/contracts/middleware/PaymentManager.sol
394 emit PaymentBreakdown(
395 operator, challenge.fromTaskNumber, challenge.toTaskNumber, challenge.amount1, challenge.amount2
396: );
File: src/contracts/core/Slasher.sol
464: emit MiddlewareTimesAdded(operator, _operatorToMiddlewareTimes[operator].length - 1, next.stalestUpdateBlock, next.latestServeUntilBlock);
File: src/contracts/core/StrategyManager.sol
669: emit Deposit(depositor, token, strategy, shares);
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: }
File: src/contracts/pods/DelayedWithdrawalRouter.sol
100 function setWithdrawalDelayBlocks(uint256 newValue) external onlyOwner {
101 _setWithdrawalDelayBlocks(newValue);
102: }
File: src/contracts/pods/EigenPodManager.sol
161 function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external onlyOwner {
162 _updateBeaconChainOracle(newBeaconChainOracle);
163: }
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: }
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: }
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: }
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: }
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: }
File: script/whitelist/Staker.sol
27 function callAddress(address implementation, bytes memory data) external onlyOwner returns(bytes memory) {
...
47: }
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: }
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: }
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: }
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: }
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: }
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),
File: src/contracts/pods/DelayedWithdrawalRouter.sol
/// @audit uint32
64: blockCreated: uint32(block.number)
File: src/contracts/pods/EigenPod.sol
/// @audit uint32
455: mostRecentWithdrawalBlockNumber = uint32(block.number);
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;
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
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 {
File: src/contracts/pods/EigenPodManager.sol
/// @audit missing __Ownable_init()
31: contract EigenPodManager is Initializable, OwnableUpgradeable, Pausable, IEigenPodManager, EigenPodPausingConstants {
File: src/contracts/pods/EigenPod.sol
/// @audit missing __ReentrancyGuard_init()
34: contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants {
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
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))
There is one instance of this issue:
File: src/contracts/core/StrategyManager.sol
848: strategyWhitelister = newStrategyWhitelister;
[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));
File: src/contracts/pods/EigenPodManager.sol
200 keccak256(abi.encodePacked(
201 beaconProxyBytecode,
202 abi.encode(eigenPodBeacon, "")
203: )) //bytecode
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
File: src/contracts/pods/DelayedWithdrawalRouter.sol
11 contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable, Pausable, IDelayedWithdrawalRouter {
...
178: }
File: src/contracts/pods/EigenPodManager.sol
31 contract EigenPodManager is Initializable, OwnableUpgradeable, Pausable, IEigenPodManager, EigenPodPausingConstants {
...
227: }
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");
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,
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);
File: lib/openzeppelin-contracts/contracts/utils/Create2.sol
37: require(bytecode.length != 0, "Create2: bytecode length is zero");
File: src/contracts/pods/DelayedWithdrawalRouter.sol
61: if (withdrawalAmount != 0) {
159: if (amountToSend != 0) {
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) {
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) {
File: lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol
84: if (valueIndex != 0) {
118: return set._indexes[value] != 0;
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");
File: lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol
81: (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
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,
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) {
File: src/contracts/libraries/Merkle.sol
51: if(index % 2 == 0) {
102: if(index % 2 == 0) {
143: while (numNodesInLayer != 0) {
File: src/contracts/operators/MerkleDelegationTerms.sol
113: if (amountToSend != 0) {
File: src/contracts/permissions/Pausable.sol
44: require(_paused == 0, "Pausable: contract is paused");
File: src/contracts/middleware/PaymentManager.sol
203: if (operatorToPayment[msg.sender].fromTaskNumber == 0) {
410: if (diff == 1) {
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) {
File: lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol
55: (value == 0) || (token.allowance(address(this), spender) == 0),
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) {
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.
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) {
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,
File: lib/forge-std/src/StdStorage.sol
51: if (reads.length == 1) {
154: if (v == 0) return false;
155: if (v == 1) return true;
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))));
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) {
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) {
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");
File: src/contracts/middleware/VoteWeigherBaseStorage.sol
67: require(_NUMBER_OF_QUORUMS != 0, "VoteWeigherBaseStorage.constructor: _NUMBER_OF_QUORUMS == 0");
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;
File: src/contracts/libraries/BeaconChainProofs.sol
65: uint256 internal constant SLOT_INDEX = 0;
File: src/contracts/pods/DelayedWithdrawalRouter.sol
16: uint8 internal constant PAUSED_DELAYED_WITHDRAWAL_CLAIMS = 0;
File: src/contracts/core/DelegationManager.sol
25: uint8 internal constant PAUSED_NEW_DELEGATION = 0;
File: src/contracts/pods/EigenPodPausingConstants.sol
10: uint8 internal constant PAUSED_NEW_EIGENPODS = 0;
File: src/contracts/middleware/example/HashThreshold.sol
26: uint32 public taskNumber = 0;
27: uint32 public latestServeUntilBlock = 0;
File: src/contracts/permissions/Pausable.sol
22: uint256 constant internal UNPAUSE_ALL = 0;
File: src/contracts/middleware/PaymentManager.sol
24: uint8 constant internal PAUSED_NEW_PAYMENT_COMMIT = 0;
File: src/contracts/core/Slasher.sol
24: uint256 private constant HEAD = 0;
26: uint8 internal constant PAUSED_OPT_INTO_SLASHING = 0;
File: src/contracts/strategies/StrategyBase.sol
22: uint8 internal constant PAUSED_DEPOSITS = 0;
File: src/contracts/core/StrategyManager.sol
38: uint8 internal constant PAUSED_DEPOSITS = 0;
File: src/contracts/libraries/StructuredLinkedList.sol
12: uint256 private constant _NULL = 0;
13: uint256 private constant _HEAD = 0;
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";
File: src/contracts/pods/BeaconChainOracle.sol
4: import "@openzeppelin/contracts/access/Ownable.sol";
File: src/contracts/pods/DelayedWithdrawalRouter.sol
6: import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
File: src/contracts/pods/EigenPod.sol
11: import "../libraries/Endian.sol";
File: src/contracts/pods/EigenPodManager.sol
5: import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
9: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
File: src/contracts/interfaces/IEigenPod.sol
4: import "../libraries/BeaconChainProofs.sol";
File: script/M1_Deploy.s.sol
7: import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
File: lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol
6: import "../utils/ContextUpgradeable.sol";
File: src/contracts/middleware/PaymentManager.sol
5: import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
File: src/contracts/middleware/RegistryBase.sol
4: import "@openzeppelin/contracts/utils/math/Math.sol";
File: src/contracts/core/Slasher.sol
9: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
File: src/contracts/strategies/StrategyBase.sol
7: import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
There are 14 instances of this issue:
File: script/Allocate.s.sol
47: uint256 wethAmount = 100000000000000000000;
File: src/contracts/pods/EigenPod.sol
41: uint256 internal constant VERIFY_OVERCOMMITTED_WINDOW_BLOCKS = 50400;
File: src/contracts/middleware/example/HashThreshold.sol
78: require(stakeSigned >= 666667 * uint256(totalStake) / 1000000, "Need more than 2/3 of stake to sign");
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")
File: lib/forge-std/src/StdCheats.sol
216: } else if (chainId == 42161 || chainId == 421613) {
219: } else if (chainId == 43114 || chainId == 43113) {
File: lib/forge-std/src/StdMath.sol
10: return 57896044618658097711785492504343953926634992332820282019728792003956564819968;
[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
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";
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";
File: src/contracts/interfaces/IDelegationManager.sol
4: import "./IDelegationTerms.sol";
File: src/contracts/interfaces/IEigenPodManager.sol
4: import "./IStrategyManager.sol";
5: import "./IEigenPod.sol";
6: import "./IBeaconChainOracle.sol";
7: import "./IPausable.sol";
File: src/contracts/interfaces/IEigenPod.sol
4: import "../libraries/BeaconChainProofs.sol";
5: import "./IEigenPodManager.sol";
6: import "./IBeaconChainOracle.sol";
File: src/contracts/interfaces/IPausable.sol
4: import "../interfaces/IPauserRegistry.sol";
File: src/contracts/interfaces/IServiceManager.sol
4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5: import "./IDelegationManager.sol";
File: src/contracts/interfaces/IStrategyManager.sol
4: import "./IStrategy.sol";
5: import "./ISlasher.sol";
6: import "./IDelegationManager.sol";
File: src/contracts/interfaces/IStrategy.sol
4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
File: src/contracts/libraries/BeaconChainProofs.sol
5: import "./Merkle.sol";
6: import "../libraries/Endian.sol";
File: src/contracts/permissions/Pausable.sol
5: import "../interfaces/IPausable.sol";
File: src/contracts/permissions/PauserRegistry.sol
4: import "../interfaces/IPauserRegistry.sol";
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";
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";
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";
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";
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) {
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
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
There are 2 instances of this issue:
File: src/contracts/core/StrategyManager.sol
671: return shares;
File: src/contracts/strategies/StrategyBase.sol
111: return newShares;
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) {
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),
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);
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)
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);
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);
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;
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;
File: src/contracts/pods/EigenPod.sol
2: pragma solidity =0.8.12;
File: src/contracts/strategies/StrategyBase.sol
2: pragma solidity =0.8.12;
[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)");
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;
File: src/contracts/pods/EigenPodManager.sol
/// @audit seen in src/contracts/core/StrategyManagerStorage.sol
49: ISlasher public immutable slasher;
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;
File: src/contracts/strategies/StrategyBase.sol
/// @audit seen in src/contracts/pods/EigenPodManager.sol
31: IStrategyManager public immutable strategyManager;
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
File: src/contracts/interfaces/IEigenPod.sol
85: ///@notice mapping that trackes proven partial withdrawals
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
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
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
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
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 {
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.
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
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
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
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,
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");
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));
File: src/contracts/pods/EigenPodManager.sol
37: bytes internal constant beaconProxyBytecode = hex"608060405260405161090e38038061090e83398101604081905261002291610460565b61002e82826000610035565b505061058a565b61003e83610100565b6040516001600160a01b038416907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e90600090a260008251118061007f5750805b156100fb576100f9836001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e99190610520565b836102a360201b6100291760201c565b505b505050565b610113816102cf60201b6100551760201c565b6101725760405162461bcd60e51b815260206004820152602560248201527f455243313936373a206e657720626561636f6e206973206e6f74206120636f6e6044820152641d1c9858dd60da1b60648201526084015b60405180910390fd5b6101e6816001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d79190610520565b6102cf60201b6100551760201c565b61024b5760405162461bcd60e51b815260206004820152603060248201527f455243313936373a20626561636f6e20696d706c656d656e746174696f6e206960448201526f1cc81b9bdd08184818dbdb9d1c9858dd60821b6064820152608401610169565b806102827fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5060001b6102de60201b6100641760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60606102c883836040518060600160405280602781526020016108e7602791396102e1565b9392505050565b6001600160a01b03163b151590565b90565b6060600080856001600160a01b0316856040516102fe919061053b565b600060405180830381855af49150503d8060008114610339576040519150601f19603f3d011682016040523d82523d6000602084013e61033e565b606091505b5090925090506103508683838761035a565b9695505050505050565b606083156103c65782516103bf576001600160a01b0385163b6103bf5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610169565b50816103d0565b6103d083836103d8565b949350505050565b8151156103e85781518083602001fd5b8060405162461bcd60e51b81526004016101699190610557565b80516001600160a01b038116811461041957600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561044f578181015183820152602001610437565b838111156100f95750506000910152565b6000806040838503121561047357600080fd5b61047c83610402565b60208401519092506001600160401b038082111561049957600080fd5b818501915085601f8301126104ad57600080fd5b8151818111156104bf576104bf61041e565b604051601f8201601f19908116603f011681019083821181831017156104e7576104e761041e565b8160405282815288602084870101111561050057600080fd5b610511836020830160208801610434565b80955050505050509250929050565b60006020828403121561053257600080fd5b6102c882610402565b6000825161054d818460208701610434565b9190910192915050565b6020815260008251806020840152610576816040850160208701610434565b601f01601f19169190910160400192915050565b61034e806105996000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f260279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610249565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a2565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020d578251610206576001600160a01b0385163b6102065760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610217565b610217838361021f565b949350505050565b81511561022f5781518083602001fd5b8060405162461bcd60e51b81526004016101fd91906102be565b60006020828403121561025b57600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028d578181015183820152602001610275565b8381111561029c576000848401525b50505050565b600082516102b4818460208701610272565b9190910192915050565b60208152600082518060208401526102dd816040850160208701610272565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d51e81d3bc5ed20a26aeb05dce7e825c503b2061aa78628027300c8d65b9d89a64736f6c634300080c0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564";
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");
[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;
File: src/contracts/pods/EigenPod.sol
140: uint256 _REQUIRED_BALANCE_WEI
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`.
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
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
File: src/contracts/interfaces/IEigenPod.sol
/// @audit trackes
85: ///@notice mapping that trackes proven partial withdrawals
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.
File: src/contracts/interfaces/IStrategyManager.sol
/// @audit targetting
79: * targetting stakers who may be attempting to undelegate.
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
File: src/contracts/libraries/BeaconChainProofs.sol
/// @audit exection
83: // in exection payload header
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.
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
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: // ┏━━━┓━┏┓━┏┓━━┏━━━┓━━┏━━━┓━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━┏┓━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━━━━━━━┏┓━
There is one instance of this issue:
File: src/contracts/libraries/Endian.sol
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) {
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;
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
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);
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
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);
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) {
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) {
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) {
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))
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) {
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);
File: src/contracts/interfaces/IETHPOSDeposit.sol
19: event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index);
File: src/contracts/permissions/Pausable.sol
26: event Paused(address indexed account, uint256 newPausedStatus);
29: event Unpaused(address indexed account, uint256 newPausedStatus);
File: src/contracts/permissions/PauserRegistry.sol
17: event PauserChanged(address previousPauser, address newPauser);
19: event UnpauserChanged(address previousUnpauser, address newUnpauser);
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);
File: src/contracts/pods/EigenPodManager.sol
64: event BeaconChainETHDeposited(address indexed podOwner, uint256 amount);
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);
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;
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.
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
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]`).
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
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
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
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) {
File: src/contracts/permissions/Pausable.sol
/// @audit _initializePauser() came earlier
71: function pause(uint256 newPausedStatus) external onlyPauser {
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) {
File: src/contracts/pods/EigenPod.sol
/// @audit _processPartialWithdrawal() came earlier
437 function withdrawRestakedBeaconChainETH(
438 address recipient,
439 uint256 amountWei
440 )
441 external
442: onlyEigenPodManager
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) {
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;
File: src/contracts/permissions/Pausable.sol
/// @audit function paused came earlier
115: uint256[48] private __gap;
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;
File: src/contracts/pods/EigenPodManager.sol
/// @audit function getBeaconChainStateRoot came earlier
226: uint256[48] private __gap;
File: src/contracts/pods/EigenPod.sol
/// @audit function _sendETH came earlier
473: uint256[46] private __gap;
File: src/contracts/strategies/StrategyBase.sol
/// @audit function _tokenBalance came earlier
250: uint256[48] private __gap;
[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;
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];
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");
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];
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);
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;
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();
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;
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);
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 {
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 {
File: src/contracts/strategies/StrategyBase.sol
56: function _initializeStrategyBase(IERC20 _underlyingToken, IPauserRegistry _pauserRegistry) internal onlyInitializing {
[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;
File: src/contracts/strategies/StrategyBase.sol
/// @audit require() on line 132
137: uint256 updatedTotalShares = priorTotalShares - amountShares;
File: src/contracts/core/StrategyManager.sol
/// @audit if-condition on line 190
191: uint256 debt = amount - userShares;
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);
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++) {
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) {
[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++) {
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) {
File: src/contracts/libraries/Merkle.sol
137: for (uint i = 0; i < numNodesInLayer; i++) {
145: for (uint i = 0; i < numNodesInLayer; i++) {
File: src/contracts/pods/DelayedWithdrawalRouter.sol
115: for (uint256 i = 0; i < claimableDelayedWithdrawalsLength; i++) {
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");
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");
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");
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");
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");
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");
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");
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");
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
File: src/contracts/interfaces/IBeaconChainOracle.sol
/// @audit latestConfirmedOracleBlockNumber(), beaconStateRootAtBlockNumber(), isOracleSigner(), hasVoted(), stateRootVotes(), totalOracleSigners(), threshold(), setThreshold(), addOracleSigners(), removeOracleSigners(), voteForBeaconChainStateRoot()
8: interface IBeaconChainOracle {
File: src/contracts/interfaces/IDelayedWithdrawalRouter.sol
/// @audit createDelayedWithdrawal(), claimDelayedWithdrawals(), claimDelayedWithdrawals(), setWithdrawalDelayBlocks(), userWithdrawals(), claimableUserDelayedWithdrawals(), userDelayedWithdrawalByIndex(), userWithdrawalsLength(), canClaimDelayedWithdrawal(), withdrawalDelayBlocks()
4: interface IDelayedWithdrawalRouter {
File: src/contracts/interfaces/IDelegationManager.sol
/// @audit registerAsOperator(), delegateTo(), delegateToBySignature(), undelegate(), delegatedTo(), delegationTerms(), operatorShares(), increaseDelegatedShares(), decreaseDelegatedShares(), isDelegated(), isNotDelegated(), isOperator()
15: interface IDelegationManager {
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 {
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 {
File: src/contracts/interfaces/IETHPOSDeposit.sol
/// @audit deposit(), get_deposit_root(), get_deposit_count()
17: interface IETHPOSDeposit {
File: src/contracts/interfaces/IPausable.sol
/// @audit pauserRegistry(), pause(), pauseAll(), unpause(), paused()
10: interface IPausable {
File: src/contracts/interfaces/IPauserRegistry.sol
/// @audit pauser(), unpauser()
8: interface IPauserRegistry {
File: src/contracts/interfaces/IServiceManager.sol
/// @audit taskNumber(), freezeOperator(), recordFirstStakeUpdate(), recordStakeUpdate(), recordLastStakeUpdateAndRevokeSlashingAbility(), latestServeUntilBlock()
11: interface IServiceManager {
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 {
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 {
File: src/contracts/interfaces/IStrategy.sol
/// @audit deposit(), withdraw(), sharesToUnderlying(), underlyingToShares(), userUnderlying(), sharesToUnderlyingView(), underlyingToSharesView(), userUnderlyingView(), underlyingToken(), totalShares(), explanation()
11: interface IStrategy {
File: src/contracts/permissions/Pausable.sol
/// @audit pause(), pauseAll(), unpause(), paused()
15: contract Pausable is IPausable {
File: src/contracts/permissions/PauserRegistry.sol
/// @audit setPauser(), setUnpauser()
10: contract PauserRegistry is IPauserRegistry {
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 {
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 {
File: src/contracts/pods/EigenPod.sol
/// @audit initialize(), stake(), verifyWithdrawalCredentialsAndBalance(), verifyOvercommittedStake(), verifyAndProcessWithdrawal(), withdrawRestakedBeaconChainETH(), withdrawBeforeRestaking()
34: contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants {
File: src/contracts/strategies/StrategyBase.sol
/// @audit initialize(), underlyingToSharesView(), underlyingToShares(), userUnderlyingView(), userUnderlying(), shares()
19: contract StrategyBase is Initializable, Pausable, IStrategy {
// 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;
File: src/contracts/pods/EigenPod.sol
73: bool public hasRestaked;
79: mapping(uint40 => mapping(uint64 => bool)) public provenPartialWithdrawal;
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++) {
File: src/contracts/libraries/Merkle.sol
137: for (uint i = 0; i < numNodesInLayer; i++) {
145: for (uint i = 0; i < numNodesInLayer; i++) {
File: src/contracts/pods/DelayedWithdrawalRouter.sol
115: for (uint256 i = 0; i < claimableDelayedWithdrawalsLength; i++) {
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: );
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));
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);
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;
File: src/contracts/pods/DelayedWithdrawalRouter.sol
24: uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 50400;
File: src/contracts/pods/EigenPod.sol
53: uint64 public immutable REQUIRED_BALANCE_GWEI;
56: uint256 public immutable REQUIRED_BALANCE_WEI;
<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 JUMP
s 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);
File: src/contracts/libraries/Merkle.sol
133: uint256 numNodesInLayer = leaves.length / 2;
[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");
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");
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");
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");
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");
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");
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");
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");
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");
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 {
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 {
File: src/contracts/permissions/PauserRegistry.sol
32: function setPauser(address newPauser) external onlyUnpauser {
37: function setUnpauser(address newUnpauser) external onlyUnpauser {
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 {
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) {
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 {
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
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)
File: src/contracts/core/StrategyManagerStorage.sol
72: constructor(IDelegationManager _delegation, IEigenPodManager _eigenPodManager, ISlasher _slasher) {
File: src/contracts/permissions/PauserRegistry.sol
26: constructor(address _pauser, address _unpauser) {
File: src/contracts/pods/DelayedWithdrawalRouter.sol
44: constructor(IEigenPodManager _eigenPodManager) {
File: src/contracts/pods/EigenPodManager.sol
76: constructor(IETHPOSDeposit _ethPOS, IBeacon _eigenPodBeacon, IStrategyManager _strategyManager, ISlasher _slasher) {
File: src/contracts/pods/EigenPod.sol
136 constructor(
137 IETHPOSDeposit _ethPOS,
138 IDelayedWithdrawalRouter _delayedWithdrawalRouter,
139 IEigenPodManager _eigenPodManager,
140: uint256 _REQUIRED_BALANCE_WEI
File: src/contracts/strategies/StrategyBase.sol
46: constructor(IStrategyManager _strategyManager) {
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