Liquid Staking

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

Remove Splitter Will Not Work In Certain Situations

Summary

The LSTRewardsSplitterController::removeSplitter function is responsible for removing a splitter and withdrawing all associated funds. The funds are transferred to an account specified as a parameter. Before this happens, the function calls splitRewards on the splitter to distribute any outstanding rewards to fee receivers. However, there is a flaw in this process: after splitting rewards, the function attempts to withdraw the original balance (before the split) instead of the updated balance, which will likely cause the transaction to revert if the balance has changed.

Vulnerability Details

Consider the following scenario to illustrate the issue:

  1. A splitter has a balance of 100 tokens.

  2. After splitting the rewards, part of the balance (e.g., 10 tokens) is distributed to fee receivers, leaving a remaining balance of 90 tokens.

  3. The removeSplitter function, however, tries to withdraw the original balance of 100 tokens, which exceeds the actual available balance of 90 tokens. This mismatch causes a failure or revert when the withdrawal is attempted.

This issue arises due to the fact that the function does not update the balance after calling splitRewards. It attempts to withdraw the pre-split balance, which is incorrect.

The vulnerability can be observed in the code snippet below:

function removeSplitter(address _account) external onlyOwner {
ILSTRewardsSplitter splitter = splitters[_account];
if (address(splitter) == address(0)) revert SplitterNotFound();
uint256 balance = IERC20(lst).balanceOf(address(splitter)); // Original balance is captured
uint256 principalDeposits = splitter.principalDeposits();
if (balance != 0) {
if (balance != principalDeposits) splitter.splitRewards(); // Split rewards, changing the balance
splitter.withdraw(balance, _account); // Attempts to withdraw the old balance, likely causing a revert
}

In the above function, the balance is initially fetched before the rewards are split. This results in an outdated balance being passed to the withdraw function, leading to a revert if the balance has changed due to the rewards distribution.

Impact

This vulnerability can cause the removeSplitter function to revert when attempting to withdraw funds, preventing the successful removal of the splitter and the withdrawal of remaining funds. This could halt operations or cause disruptions, especially if multiple splitters are being managed within the system.

Tools Used

Manual review

Recommendations

To fix this issue, the balance should be updated after the splitRewards call to ensure that the correct amount is withdrawn. Below is the recommended change to the removeSplitter function:

function removeSplitter(address _account) external onlyOwner {
ILSTRewardsSplitter splitter = splitters[_account];
if (address(splitter) == address(0)) revert SplitterNotFound();
uint256 balance = IERC20(lst).balanceOf(address(splitter)); // Initial balance before rewards split
uint256 principalDeposits = splitter.principalDeposits();
if (balance != 0) {
if (balance != principalDeposits) splitter.splitRewards(); // Split rewards, changing the balance
+ balance = IERC20(lst).balanceOf(address(splitter)); // Update balance after rewards are split
splitter.withdraw(balance, _account); // Withdraw updated balance
}

By re-fetching the balance after the splitRewards function call, the correct (post-split) balance will be withdrawn, preventing any reverts and ensuring smooth operation.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

In `removeSplitter` the `principalDeposits` should be used as an input for `withdraw` instead of balance after splitting the existing rewards.

Support

FAQs

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