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
Rank: 33/43
Findings: 1
Award: $90.02
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: neutiyoo
Also found by: 0xSmartContract, 0xnev, Aymen0909, QiuhaoLi, ReyAdmirado, clayj, ihtishamsudo, naman1778, niser93, pontifex, tonisives, turvy_fuzz
90.0161 USDC - $90.02
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.
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
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.
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.
calldata
instead of memory
for read-only arguments in external functions saves gasWhen 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.
if the functions are required by the interface, the contract should inherit from that interface and use override keyword
Using ternary operator instead of the if else statement saves gas.
saves 6 gas per instance
Pausable.sol#L57 2 instances
before transfer we should check for amount being 0 so the function doesnt run when its not gonna do anything
amount
should be checked
stops usage of a keccak256() every time the constant is used
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
DelayedWithdrawalRouter.sol#L133 2 instances
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)
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
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