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: 8/43
Findings: 2
Award: $2,109.36
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Cyfrin
Also found by: Josiah, QiuhaoLi, RaymondFam
2037.7578 USDC - $2,037.76
https://github.com/code-423n4/2023-04-eigenlayer/blob/main/src/contracts/core/StrategyManager.sol#L195-L198 https://github.com/code-423n4/2023-04-eigenlayer/blob/main/src/contracts/core/StrategyManager.sol#L699-L705 https://github.com/code-423n4/2023-04-eigenlayer/blob/main/src/contracts/core/StrategyManager.sol#L807-L814
The modifier onlyNotFrozen()
is intuitive such that the staker will be frozen when the delegated operator is frozen. However, not utilizing it in recordOvercommittedBeaconChainETH()
and undelegate()
could allow the Beacon Chain validator to undelegate from the frozen operator and escape the slash.
Let's assume the validator has only beaconChainETHStrategy
in his/her stakerStrategy
. It happens that the operator delegated is doing something not right and now frozen. The validator, after realizing this, purposely makes himself/herself over committed resulting in all shares removed from the enshrined beacon chain ETH strategy:
// removes shares for the enshrined beacon chain ETH strategy if (amount != 0) { _removeShares(overcommittedPodOwner, beaconChainETHStrategyIndex, beaconChainETHStrategy, amount); }
Since amount
is REQUIRED_BALANCE_WEI
, the same amount virtually deposited for new ETH validator, assuming that no rewards have been given, no existing shares is left and hence the strategy is removed from the depositor's dynamic array of strategies.
// if no existing shares, remove the strategy from the depositor's dynamic array of strategies if (userShares == 0) { _removeStrategyFromStakerStrategyList(depositor, strategyIndex, strategy); // return true in the event that the strategy was removed from stakerStrategyList[depositor] return true; }
The validator can now undelegate from the operator:
/** * @notice If the `depositor` has no existing shares, then they can `undelegate` themselves. * This allows people a "hard reset" in their relationship with EigenLayer after withdrawing all of their stake. */ function _undelegate(address depositor) internal { require(stakerStrategyList[depositor].length == 0, "StrategyManager._undelegate: depositor has active deposits"); delegation.undelegate(depositor); }
The validator is now unfrozen and will escape the slash. On top of that, he/she is free to make a full withdrawal undeterred.
It is recommended imposing onlyNotFrozen
on recordOvercommittedBeaconChainETH()
and undelegate()
to circumvent the leaked path.
Timing
#0 - c4-pre-sort
2023-05-09T13:31:48Z
0xSorryNotSorry marked the issue as duplicate of #210
#1 - c4-judge
2023-06-01T15:05:14Z
GalloDaSballo marked the issue as duplicate of #210
#2 - c4-judge
2023-06-05T13:31:03Z
GalloDaSballo marked the issue as not a duplicate
#3 - c4-judge
2023-06-05T13:31:11Z
GalloDaSballo marked the issue as duplicate of #408
#4 - c4-judge
2023-06-06T14:54:10Z
GalloDaSballo marked the issue as satisfactory