Variable side cannot always withdraw fee share
Description
During a vault's lifetime, a variable-side depositor can withdraw both their share of the yields and their share of the early exit fees charged by withdrawing fixed-side depositors. This is implemented in the withdraw
function:
function withdraw(uint256 side) external {
// [...] [ case !isEnded() && side == VARIABLE ]
require(variableToVaultOngoingWithdrawalRequestIds[msg.sender].length == 0, "WAR");
// [...]
if (lidoStETHBalance > fixedETHDeposits) {
// [...]
if (ethAmountOwed >= minStETHWithdrawalAmount()) {
// [...]
variableToVaultOngoingWithdrawalRequestIds[msg.sender] = requestWithdrawViaETH(
msg.sender,
ethAmountOwed
);
// [...]
return;
}
}
// there are no staking earnings that can be withdrawn but there are fixed side early withdrawal fees
if (feeEarnings > 0) {
uint256 feeEarningsShare = calculateVariableFeeEarningsShare();
require(feeEarningsShare > 0, "NZW");
transferWithdrawnFunds(msg.sender, feeEarningsShare);
emit VariableFundsWithdrawn(feeEarningsShare, msg.sender, isStarted(), isEnded());
return;
}
However, note that the first type of withdrawal is tried first and, if it succeeds in queueing a withdrawal, the fee earnings are not withdrawn. Also, if the user has an ongoing withdrawal in the queue, they also cannot withdraw due to the require.
Impact
Even though the feeEarnings
consists of ETH held in the contract, variable-side depositors inconveniently cannot withdraw it unless they also withdraw their yield earnings and wait for that withdrawal to complete.
Recommendations
We recommend processing the fee earnings withdrawal even if there is a pending yield withdrawal and also even if the withdrawal call successfully caused the queueing of a new yield withdrawal.
Remediation
This issue has been acknowledged by Saffron, and a fix was implemented in commit ee03623d↗.