EigenLayer Contest - ReyAdmirado's results

Enabling restaking of staked Ether, to be used as cryptoeconomic security for decentralized protocols and applications.

General Information

Platform: Code4rena

Start Date: 27/04/2023

Pot Size: $90,500 USDC

Total HM: 4

Participants: 43

Period: 7 days

Judge: GalloDaSballo

Id: 233

League: ETH

EigenLayer

Findings Distribution

Researcher Performance

Rank: 33/43

Findings: 1

Award: $90.02

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Labels

bug
G (Gas Optimization)
grade-b
high quality report
G-11

Awards

90.0161 USDC - $90.02

External Links

1. expressions for constant values such as a call to keccak256(), should use immutable rather than constant

2. Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate (extra to what bot found)

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.

nonces and stakerStrategyShares and stakerStrategyList and numWithdrawalsQueued and beaconChainETHSharesToDecrementOnWithdrawal all can be combined. this will not save any storage slot but the access gas save will be big gas save.

3. state variables should be cached in stack variables rather than re-reading them from storage

Caching of a state variable replace 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.

validatorStatus[validatorIndex] has a high probability to be read twice from storage in #L333 and #L355. this can be avoided by caching it before #L333

withdrawalDelayBlocks can be cached before the loop because it doesnt change, but it is being read every time in loop

4. not using the named return variables when a function returns, wastes deployment gas

5. it costs more gas to initialize non-constant/non-immutable variables to zero than to let the default of zero be applied

6. require() or revert() statements that check input arguments should be at the top of the function (other than the ones in known issues)

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.

require in #L766 is much cheaper than all other above it in the function, so it is better to check it first to save gas.

7. use a more recent version of solidity

use 0.8.19 for a bunch of small improvements that result in big gas saves combined

Use a solidity version of at least 0.8.13 to get the ability to use using for with a list of free functions Use a solidity version of at least 0.8.15 because the conditions necessary for inlining are relaxed. Benchmarks show that the change significantly decreases the bytecode size (which impacts the deployment cost) while the effect on the runtime gas usage is smaller. Use a solidity version of at least 0.8.17 to get prevention of the incorrect removal of storage writes before calls to Yul functions that conditionally terminate the external EVM call; Simplify the starting offset of zero-length operations to zero. More efficient overflow checks for multiplication.

8. using calldata instead of memory for read-only arguments in external functions saves gas

When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution.

9. internal or private functions not called by the contract or inherited ones should be removed to save deployment gas

if the functions are required by the interface, the contract should inherit from that interface and use override keyword

10. abi.encode() is less efficient than abi.encodepacked()

11. Ternary over if ... else

Using ternary operator instead of the if else statement saves gas.

12. Use assembly to check for address(0)

saves 6 gas per instance

13. before some functions we should check some variables for possible gas save

before transfer we should check for amount being 0 so the function doesnt run when its not gonna do anything

amount should be checked

14. instead of calculating keccak256() every time the constant is used pre calculate them before and only give the result to a constant

stops usage of a keccak256() every time the constant is used

15. Non-strict inequalities are cheaper than strict ones

In the EVM, there is no opcode for non-strict inequalities (>=, <=) and two operations are performed (> + = or < + =). consider replacing >= with the strict counterpart > and add - 1 to the second side

16. part of the code can be pre calculated

these parts of the code can be pre calculated and given to the contract as constants this will stop the use of extra operations even if its for code readability consider putting comments instead.

keccak256(bytes("EigenLayer")) can be precalculated to stop using a extra keccak256 operation

2**BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT

2**BEACON_STATE_FIELD_TREE_HEIGHT

2**VALIDATOR_FIELD_TREE_HEIGHT

2**ETH1_DATA_FIELD_TREE_HEIGHT

32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT)

32 * ((BALANCE_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT

2**BLOCK_ROOTS_TREE_HEIGHT

2**WITHDRAWAL_FIELD_TREE_HEIGHT

2**WITHDRAWALS_TREE_HEIGHT

32 * (BEACON_STATE_FIELD_TREE_HEIGHT + BLOCK_ROOTS_TREE_HEIGHT)

32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT + WITHDRAWALS_TREE_HEIGHT + 1)

32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT)

17. Use selfbalance() instead of address(this).balance

Use assembly when getting a contract's balance of ETH.

You can use selfbalance() instead of address(this).balance when getting your contract's balance of ETH to save gas. Additionally, you can use balance(address) instead of address.balance() when getting an external contract's balance of ETH.

Saves 15 gas when checking internal balance, 6 for external

18. state variables can be sorted better to reduce gas usage

If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables are also cheaper. saves ~84 gas

podOwner and mostRecentWithdrawalBlockNumber are in the same slot and restakedExecutionLayerGwei and hasRestaked are in another slot together. but this is not the most optimal way and we can bring hasRestaked near podOwner because they are used in the same function twice and these reads gonna be cheaper. restakedExecutionLayerGwei and hasRestaked are never both accessed in the same function. so bring hasRestaked #L73 near podOwner #L59 and mostRecentWithdrawalBlockNumber #L66

#0 - c4-pre-sort

2023-05-09T14:47:30Z

0xSorryNotSorry marked the issue as high quality report

#1 - c4-judge

2023-06-01T16:58:59Z

GalloDaSballo marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter