EigenLayer Contest - pontifex'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: 11/43

Findings: 2

Award: $1,533.95

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: juancito

Also found by: MiloTruck, Ruhum, SpicyMeatball, bin2chen, evmboi32, pontifex, sashik_eth, volodya, yjrwkk

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
duplicate-205

Awards

1443.9307 USDC - $1,443.93

External Links

Lines of code

https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/core/StrategyManager.sol#L536-L579

Vulnerability details

Temporary blocking withdrawals because of slashQueuedWithdrawal function incorrectness

##Impact The incorrectness of the slashQueuedWithdrawalcan block withdraw operations till queuedWithdrawal argument will be changed to exclude strategies with PAUSED_WITHDRAWALS parameter.

Proof of Concept

There is an indicesToSkip optional argument for bypassing strategies, which can't be withdrawn. indicesToSkip contents indices of such strategiess. The loop in the L560 lets you sort strategies, but increment ++i is placed in the wrong line. If the logic branch goes into skipping strategie, the i variable stays the same. So the next iteration will be with the same strategie index.

         for (uint256 i = 0; i < strategiesLength;) {
             // check if the index i matches one of the indices specified in the `indicesToSkip` array
             if (indicesToSkipIndex < indicesToSkip.length && indicesToSkip[indicesToSkipIndex] == i) {
                 unchecked {
                     ++indicesToSkipIndex;
                 }
             } else {
                 if (queuedWithdrawal.strategies[i] == beaconChainETHStrategy){
                      //withdraw the beaconChainETH to the recipient
                     _withdrawBeaconChainETH(queuedWithdrawal.depositor, recipient, queuedWithdrawal.shares[i]);
                 } else {
                     // tell the strategy to send the appropriate amount of funds to the recipient
                     queuedWithdrawal.strategies[i].withdraw(recipient, tokens[i], queuedWithdrawal.shares[i]);
                 }
                 unchecked {
                     ++i;
                 }
             }
         }

https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/core/StrategyManager.sol#L575

Tools Used

manual review

Put the increment in the bottom of the loop.

Assessed type

Loop

#0 - c4-pre-sort

2023-05-09T13:09:17Z

0xSorryNotSorry marked the issue as duplicate of #205

#1 - c4-judge

2023-06-08T12:23:22Z

GalloDaSballo marked the issue as satisfactory

#2 - c4-judge

2023-06-08T12:23:30Z

GalloDaSballo changed the severity to 3 (High Risk)

Findings Information

Labels

bug
G (Gas Optimization)
grade-b
G-03

Awards

90.0161 USDC - $90.02

External Links

GAS-1 Declare strategiesLength caching variable in the top of the function body

Declaring strategiesLength caching variable in the top of the function body lets use the variable in other statements. There are three instances:

341:    {
342:        require(strategies.length == shares.length, "StrategyManager.queueWithdrawal: input length mismatch");
...
358:        for (uint256 i = 0; i < strategies.length;) {
...    
362:                require(strategies.length == 1,

https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/core/StrategyManager.sol#L342-L362

494:    {
495:        require(tokens.length == strategies.length, "StrategyManager.slashShares: input length mismatch");
496:        uint256 strategyIndexIndex;
497:        uint256 strategiesLength = strategies.length;
498:        for (uint256 i = 0; i < strategiesLength;) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/core/StrategyManager.sol#L494-L498

541:    {
542:        require(tokens.length == queuedWithdrawal.strategies.length, "StrategyManager.slashQueuedWithdrawal: input length mismatch");
...
559:        uint256 strategiesLength = queuedWithdrawal.strategies.length;
560:        for (uint256 i = 0; i < strategiesLength;) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/core/StrategyManager.sol#L541-L560

GAS-2 Use strategiesLength caching variable instead queuedWithdrawal.strategies.length

Using strategiesLength caching variable instead queuedWithdrawal.strategies.length in the line #778 will save some gas.

775:        uint256 strategiesLength = queuedWithdrawal.strategies.length;
776:        // if the withdrawer has flagged to receive the funds as tokens, withdraw from strategies
777:        if (receiveAsTokens) {
778:            require(tokens.length == queuedWithdrawal.strategies.length, "StrategyManager.completeQueuedWithdrawal: input length mismatch");

GAS-3 Use caching variable for stakerStrategyShares[depositor][strategy] in _addShares function

Using caching variable for stakerStrategyShares[depositor][strategy] in _addShares function in if in the line #63 and as an operand in the line #644 lets save about 100 gas. In the line #644 it should be look like stakerStrategyShares[depositor][strategy] = cachedValue + shares;

629:    function _addShares(address depositor, IStrategy strategy, uint256 shares) internal {
...
635:        if (stakerStrategyShares[depositor][strategy] == 0) {
...
644:        stakerStrategyShares[depositor][strategy] += shares;

https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/core/StrategyManager.sol#L629-L644

GAS-4 Use caching for <array>.length in loops

Caching indicesToSkip.length instead of looking it up in every loop can save some gas.

560:        for (uint256 i = 0; i < strategiesLength;) {
561:            // check if the index i matches one of the indices specified in the `indicesToSkip` array
562:            if (indicesToSkipIndex < indicesToSkip.length && indicesToSkip[indicesToSkipIndex] == i) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/core/StrategyManager.sol#L562

GAS-5 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 }

824:            if (amount > amountToDecrement) {
825:                beaconChainETHSharesToDecrementOnWithdrawal[staker] = 0;
826:                // decrease `amount` appropriately, so less is sent at the end
827:                amount -= amountToDecrement;

https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/core/StrategyManager.sol#L824-L827

GAS-6 Use caching variable for withdrawalDelayBlocks state variable in the loop

Using the caching variable for withdrawalDelayBlocks state variable in the loop lets save gas.

142:        while (i < maxNumberOfDelayedWithdrawalsToClaim && (delayedWithdrawalsCompletedBefore + i) < _userWithdrawalsLength) {
...    
146:            if (block.number < delayedWithdrawal.blockCreated + withdrawalDelayBlocks) {

https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/core/StrategyManager.sol#L629-L644

#0 - GalloDaSballo

2023-06-01T16:48:44Z

GAS-3 Use caching variable for stakerStrategyShares[depositor][strategy] in _addShares function 1L

Rest is OOS

#1 - c4-judge

2023-06-01T16:59:02Z

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