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: 4/43
Findings: 2
Award: $3,177.34
๐ Selected for report: 1
๐ Solo Findings: 0
๐ Selected for report: Cyfrin
Also found by: Josiah, QiuhaoLi, RaymondFam
2649.0852 USDC - $2,649.09
https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/core/StrategyManager.sol#L197 https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/core/StrategyManager.sol#L513 https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/pods/EigenPod.sol#L293 https://github.com/code-423n4/2023-04-eigenlayer/blob/5e4872358cd2bda1936c29f460ece2308af4def6/src/contracts/pods/EigenPod.sol#L396
In EigenLayer, watchers submit over-commitment proof in the event a staker's balance on the Beacon chain falls below the minimum restaked amount per validator. In such a scenario, stakersโ shares are decreased by the restaked amount. Note that when a full withdrawal is processed, stakersโ deducted shares are credited back to allow for a planned withdrawal on EigenLayer
If such a staker has delegated to an operator who gets slashed on EigenLayer, there is a possibility that this staker completely bypasses any slashing penalties. If overcommitment reduced the shares in the stakers account to 0, there is nothing available for governance to slash.
It is reasonable to assume that governance calls StrategyManager::slashShares
to reduce a percentage of shares (penalty) from all stakers who delegated to a slashed operator and then resets the frozen status of operator by calling ISlasher::resetFrozenStatus
.
By simply unstaking on the beacon chain AFTER slashing is complete (and operator is unfrozen), an over-committed staker can simply unstake on beacon chain and get back their shares that were deducted when over-commitment was recorded (refer to PoC). Note that these shares have not faced any slashing penalties.
An over-committed staker can avoid being slashed in a scenario in which their stake should be subject to slashing and so we evaluate the severity to MEDIUM.
Consider the following scenario with a chain of events in this order:
BeaconEthStrategy
.EigenPod::verifyOverCommittedStake
and Alice's shares are now decreased to 0 (Note: this will be credited back to Alice when she withdraws from the Beacon Chain using EigenPod::verifyAndProcessWithdrawal
)ISlasher::resetFrozenStatus
.Manual review
Over-commitment needs to be accounted for when slashing such that a staker is being slashed not just shares in their account, but also the amount that is temporarily debited while recording over-commitment.
Consider adding a mapping to StrategyManager
that keeps track of over-commitment for each staker and take that into consideration while slashing in StrategyManager::slashShares
.
Other
#0 - c4-pre-sort
2023-05-09T13:31:51Z
0xSorryNotSorry marked the issue as duplicate of #210
#1 - c4-judge
2023-06-01T15:04:45Z
GalloDaSballo marked the issue as not a duplicate
#2 - c4-judge
2023-06-01T15:04:51Z
GalloDaSballo marked the issue as primary issue
#3 - c4-judge
2023-06-01T15:05:36Z
GalloDaSballo marked the issue as selected for report
#4 - GalloDaSballo
2023-06-01T15:06:56Z
The Warden has shown how, due to an incorrect assumption, it's possible for an over-commited staker to avoid a slashing
The finding requires multiple external requirements:
For these reasons I agree with Medium Severity
๐ Selected for report: volodya
Also found by: 0xWaitress, 0xnev, ABA, Aymen0909, Cyfrin, QiuhaoLi, RaymondFam, btk, bughunter007, ihtishamsudo, juancito, libratus, niser93, sashik_eth
528.2547 USDC - $528.25
Endian::fromLittleEndianUint64
allows unsafe castingThe input to Endian::fromLittleEndianUint64
is not validated. It should check that the number to be cast is not greater than 2^64
in little endian notation.
In the event this library is used to cast a number greater that 2^64
, the casting will be done incorrectly; however; this is not currently the case and so we evaluate this as LOW.
For instance, if this library attempts to cast 2^65
in little endian, the output will be 1 in big endian.
Add the corresponding check to revert for casting of numbers greater than 2^64
in little endian:
// In Endian.fromLittleEndianUint64(bytes32) + if(n & 0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000 != n){ + return ERROR_LITTLE_ENDIAN_UNSAFE_UINT64_CAST() + } n = uint64(uint256(lenum >> 192)); //...
EigenPod::stake
: should be refactored to allow any stake and not just 32 etherEigenPod::stake
, callable only through EigenPodManager::stake
, allows the caller to stake 32 ether only, no more or less.
According to the sponsor, it is valid that a user could stake a different amount through the ETH2Deposit contract if they really wanted to. Therefore, to improve UX, this function can be refactored to allow ether deposits of any value.
EigenPod::mostRecentWithdrawalBlockNumber
to mostRecentWithdrawalBeforeRestakingBlockNumber
EigenPods essentially switch between 2 modalities, with only (1) possible before launch of the EigenLayer Beacon Chain Oracle:
\1) users can re-point withdrawal credentials but not yet prove they have been re-pointed via the EigenPod::verifyWithdrawalCredentialsAndBalance
. Partial & full withdrawals will increment their EigenPod balance, and they can then call EigenPod::withdrawBeforeRestaking
to withdraw any ETH in the EigenPod. Once a user proves re-pointing of withdrawal credentials for the first time (which must be proven after EigenPod::mostRecentWithdrawalBlockNumber
because they could have repointed => withdrawn => used the ETH for something else prior to EigenPod::mostRecentWithdrawalBlockNumber
) then the modality is switched to:
\2) users can no longer call EigenPod::withdrawBeforeRestaking
and must use EigenPod::withdrawRestakedBeaconChainETH
.
Therefore, EigenPos::mostRecentWithdrawalBlockNumber
should be more properly called mostRecentWithdrawalBeforeRestakingBlockNumber
.
#0 - c4-pre-sort
2023-05-09T14:34:23Z
0xSorryNotSorry marked the issue as high quality report
#1 - GalloDaSballo
2023-06-02T09:06:29Z
[L-01] Endian::fromLittleEndianUint64 allows unsafe casting R, similar to adding check for bytes%32 in merkle functions
[NC-01] EigenPod::stake: should be refactored to allow any stake and not just 32 ether R, I can see scenarios in which this would useful (e.g. getting slashed and wanting to top up)
[NC-02] Rename EigenPod::mostRecentWithdrawalBlockNumber to mostRecentWithdrawalBeforeRestakingBlockNumber R
#2 - GalloDaSballo
2023-06-02T09:06:34Z
3R unique report
#3 - c4-judge
2023-06-02T09:38:49Z
GalloDaSballo marked the issue as grade-b
#4 - GalloDaSballo
2023-06-08T10:56:16Z
A after adding DUPS
#5 - c4-judge
2023-06-08T10:56:19Z
GalloDaSballo marked the issue as grade-a