Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: high
Invalid

Incomplete Withdrawal Loop in _withdrawLiquidity Function

Summary

The _withdrawLiquidity function in the StakingPool contract contains a loop that prematurely exits after a partial withdrawal, potentially leaving user funds inaccessible when they should be available across multiple strategies.

Details

The _withdrawLiquidity function iterates through the available strategies to withdraw the requested amount. However, the loop structure is flawed:

  • If a strategy can fulfill the entire withdrawal amount, it correctly processes the withdrawal and exits the loop.

  • If a strategy can only partially fulfill the withdrawal, it processes that partial amount but then fails to continue to the next strategy.

This means that if the total requested withdrawal amount is spread across multiple strategies, the function will only withdraw from the first strategy it encounters with available funds, even if subsequent strategies could fulfill the remaining amount.

Impact

This issue can lead to several significant problems:

  1. Incomplete Withdrawals: Users may receive less than their requested withdrawal amount, even when sufficient funds are available across multiple strategies.

  2. Funds Lockup: User funds may become temporarily or permanently inaccessible if they're spread across strategies.

  3. Reduced Liquidity: The overall liquidity of the pool may appear lower than it actually is, as funds in subsequent strategies are not being utilized for withdrawals.

  4. User Trust: Incomplete or failed withdrawals when funds should be available could significantly erode user trust in the platform.

Code Snippet

function _withdrawLiquidity(uint256 _amount, bytes[] calldata _data) private {
uint256 toWithdraw = _amount;
for (uint256 i = strategies.length; i > 0; i--) {
IStrategy strategy = IStrategy(strategies[i - 1]);
uint256 strategyCanWithdrawdraw = strategy.canWithdraw();
if (strategyCanWithdrawdraw >= toWithdraw) {
strategy.withdraw(toWithdraw, _data[i - 1]);
break;
} else if (strategyCanWithdrawdraw > 0) {
strategy.withdraw(strategyCanWithdrawdraw, _data[i - 1]);
toWithdraw -= strategyCanWithdrawdraw;
}
}
}

Recommendation

To fix this issue, the loop should continue to the next strategy if a partial withdrawal occurs. Here's a corrected version of the function:

function _withdrawLiquidity(uint256 _amount, bytes[] calldata _data) private {
uint256 toWithdraw = _amount;
for (uint256 i = strategies.length; i > 0; i--) {
IStrategy strategy = IStrategy(strategies[i - 1]);
uint256 strategyCanWithdrawdraw = strategy.canWithdraw();
if (strategyCanWithdrawdraw >= toWithdraw) {
strategy.withdraw(toWithdraw, _data[i - 1]);
break;
} else if (strategyCanWithdrawdraw > 0) {
strategy.withdraw(strategyCanWithdrawdraw, _data[i - 1]);
toWithdraw -= strategyCanWithdrawdraw;
if (toWithdraw == 0) break;
}
}
require(toWithdraw == 0, "Insufficient liquidity across all strategies");
}

This modification ensures that the loop continues through all strategies until the full withdrawal amount is processed or all strategies have been checked. It also adds a final check to ensure the full amount was withdrawn, reverting the transaction if not.

Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.