A miscalculation in the logic that determines the value for totalUnbonded
prevents it from ever being different than 0.
This prevents successful withdrawals from VaultDepositController::withdraw
, and causes VaultControllerStrategy::getMinDeposits
to always return totalDeposits
, ultimately preventing WithdrawalPool::checkUpkeep
to ever return true
.
There are only two places where VaultControllerStrategy::totalUnbonded
is updated : VaultDepositController::withdraw
and VaultControllerStrategy::updateVaultGroups
. As this report will show, both of those cannot update the value of totalUnbonded
to another value than 0.
withdraw
functionThe function VaultDepositController::withdraw
begins by checking whether the withdrawal amount is greater than totalUnbonded
.
Since totalUnbonded
is always 0 due to a miscalculation, the function reverts whenever it's called, preventing any withdrawal from happening. This also prevents the assignation of totalUnbonded
to another value in this function.
updateVaultGroups
functionThe function VaultControllerStrategy::updateVaultGroups
is called only by FundFlowController
, for both operatorVCS
and communityVCS
. The function takes the parameter _nextGroupTotalUnbonded
, which is the value we are setting to totalUnbonded
.
For both operatorVCS
and communityVCS
, the variable passed as _nextGroupTotalUnbonded
is calculated by the function FundFlowController::_getVaultUpdateData
, which sets the variable nextGroupTotalUnbonded
from the result of FundFlowController::_getTotalUnbonded
.
The result variable in this function, totalUnbonded
, is computed by adding the results of vault.getPrincipalDeposits()
for each vault
passed in parameter (each vault of the group to unbond) that is not removed and for which the claim period is active. However, since we are trying to get the totalUnbonded
value of all vaults that we are about to unbond
, none of these vault can have claimPeriodActive()
return true, so the returned value of totalUnbonded
will always be 0, which will later be set to VaultControllerStrategy::updateVaultGroups
from the FundFlowController
.
updateVaultGroups
:FundFlowController::updateVaultGroups
is called. This will start the unbonding for the next group of vaults.
The data for the next group of vaults is computed using _getVaultUpdateData
, and returns 0 for the nextGroupOpVaultsTotalUnbonded
and nextGroupComVaultsTotalUnbonded
.
Vault groups are updated for operatorVCS
and communityVCS
.
The value of totalUnbonded
is set to 0.
The miscalculation of totalUnbonded
results in funds being permanently locked within vaults, as VaultDepositController::withdraw
can never be executed successfully.
Additionally, StakingPool::canWithdraw
will always return 0. Since WithdrawalPool::performUpkeep
relies on this value being different to 0 to execute, the execution of the WithdrawalPool::performUpkeep
function to always fail. While the WithdrawalPool
can still execute withdrawals with deposits from the PriorityPool
, the inability to run performUpkeep
leads to the protocol not being able to finalize the withdrawals for users from the tokens of the staking pool.
The following PoC can be copy-pasted in test/core/priorityPool/
, and run with :
npx hardhat test --grep "totalUnbonded is always 0"
The PoC proves the following points :
VaultControllerStrategy::totalUnbonded
is always 0.
VaultDepositController::withdraw
always revert.
WithdrawalPool::checkUpkeep
returns false
due to PriorityPool::canWithdraw
consistently returning 0.
Manual review.
If FundFlowController::_getTotalUnbonded
is edited to not ignore vaults that aren't in the claim period, this would fix the value of totalUnbonded
for this vulnerability, but it may impact other parts of the protocol, such as FundFlowController::updateOperatorVaultGroupAccounting
as it loops through all _vaultGroups
and not only the next one to unbound.
To mitigate this, consider creating a separate function, _getTotalUnbondedForNextVaultGroup
, to handle the unbonding process specifically.
The following code diff illustrates the recommended solution :
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.