Liquid Staking

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

Incorrect `totalStaked` Accounting in `removeStrategy()` Function

Summary

removeStrategy() function in StakingPool contract fails to update the totalStaked variable when withdrawing tokens from a removed strategy. This leads to an inconsistency between totalStaked and the actual total staked balance, affecting reward calculations, distributions, and reporting.

This bug affects users because it leads to an incorrect accounting of the total staked tokens. As a result:

  1. The totalStaked variable will not accurately represent the total amount of tokens staked in the pool and its strategies.

  2. Functions that rely on totalStaked, such as _totalStaked() and getStrategyRewards(), will return incorrect values.

Vulnerability Details

When a strategy is removed, the function withdraws all the tokens from the strategy using strategy.withdraw(totalStrategyDeposits, _strategyWithdrawalData). However, after the withdrawal, the totalStaked variable is not updated to reflect the increase in the pool's balance. As a result, totalStaked will be less than the actual total amount of staked tokens.

This bug affects the accuracy of the totalStaked variable and any functions that rely on it, such as _totalStaked() and getStrategyRewards(). Incorrect totalStaked values can lead to incorrect reward calculations, distributions, and reporting of the pool's total staked balance. https://github.com/Cyfrin/2024-09-stakelink/blob/f5824f9ad67058b24a2c08494e51ddd7efdbb90b/contracts/core/StakingPool.sol#L299-L321

function removeStrategy(
uint256 _index,
bytes memory _strategyUpdateData,
bytes calldata _strategyWithdrawalData
) external onlyOwner {
require(_index < strategies.length, "Strategy does not exist");
uint256[] memory idxs = new uint256[]();
idxs[0] = _index;
_updateStrategyRewards(idxs, _strategyUpdateData);
IStrategy strategy = IStrategy(strategies[_index]);
uint256 totalStrategyDeposits = strategy.getTotalDeposits();
if (totalStrategyDeposits > 0) {
strategy.withdraw(totalStrategyDeposits, _strategyWithdrawalData);
// @audit: totalStaked is not updated after withdrawing from the strategy
// @Fix: totalStaked += totalStrategyDeposits;
}
for (uint256 i = _index; i < strategies.length - 1; i++) {
strategies[i] = strategies[i + 1];
}
strategies.pop();
token.safeApprove(address(strategy), 0);
}

The vulnerability lies in the removeStrategy() function, specifically in the lines: https://github.com/Cyfrin/2024-09-stakelink/blob/f5824f9ad67058b24a2c08494e51ddd7efdbb90b/contracts/core/StakingPool.sol#L312-L314

if (totalStrategyDeposits > 0) {
strategy.withdraw(totalStrategyDeposits, _strategyWithdrawalData);
// @audit: Vulnerability totalStaked is not updated after withdrawing from the strategy
// @Fix: totalStaked += totalStrategyDeposits;
}

After withdrawing the tokens from the strategy, the totalStaked variable is not updated to account for the increase in the pool's balance. This leads to an inconsistency between totalStaked and the actual total staked balance.

Here's how the bug happens:

  1. When removeStrategy() is called, it first updates the rewards for the strategy being removed by calling _updateStrategyRewards().

  2. Then, it checks if the strategy has any deposits by calling strategy.getTotalDeposits(). If there are deposits (totalStrategyDeposits > 0), it withdraws all the tokens from the strategy using strategy.withdraw(totalStrategyDeposits, _strategyWithdrawalData).

  3. However, after withdrawing the tokens from the strategy, the totalStaked variable is not updated to reflect the increase in the pool's balance. This means that totalStaked will be less than the actual total amount of staked tokens.

Impact

Incorrect reward calculations and distributions based on the totalStaked value.

Tools Used

Vs

Recommendations

Update the totalStaked variable after withdrawing tokens from the strategy in the removeStrategy() function by updating totalStaked with the withdrawn amount, the contract will maintain an accurate representation of the total staked tokens.

function removeStrategy(
uint256 _index,
bytes memory _strategyUpdateData,
bytes calldata _strategyWithdrawalData
) external onlyOwner {
// ...
IStrategy strategy = IStrategy(strategies[_index]);
uint256 totalStrategyDeposits = strategy.getTotalDeposits();
if (totalStrategyDeposits > 0) {
strategy.withdraw(totalStrategyDeposits, _strategyWithdrawalData);
+ totalStaked += totalStrategyDeposits;
}
// ...
}
Updates

Lead Judging Commences

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.