Liquid Staking

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

The `removeSplitter` function still transfer the same amount before reward transfer which will revert the transaction

Summary

To remove splitter from controller, we first check that if the balance of splitter is not zero and also balance is not equal to 'principalDeposits', then we call the'splitRewards function to distribute the rewards among the fee receivers. However, the issue here is that we still have to withdraw the exact same balance amount to the splitter owner's address.

Vulnerability Details

Let us have a splitter contract with the balance of 100 LST and the total deposit of 90; the 10 LST is for rewards distribution.
So here the balance = 100 LST and principalDeposits = 90 LST are not equal, we need to distribute the rewards.

2024-09-stakelink/contracts/core/lstRewardsSplitter/LSTRewardsSplitterController.sol:134
134: uint256 balance = IERC20(lst).balanceOf(address(splitter));
135: uint256 principalDeposits = splitter.principalDeposits();
136: if (balance != 0) {
137: if (balance != principalDeposits) splitter.splitRewards();
138: splitter.withdraw(balance, _account); // @audit : the balance will revert as we will distribute the rewards and the balance will not be same as already cached??
139: }
140:

Here at Line 138 we still try to withdraw 100 LST; in fact, we do not have 100 LST available in balance.
Therefor it will revert and not allow the owner to remove the splitter.

POC : add the following test case inside test/core/lst-rewards-splitter.test.ts :

it.only('should be able to remove splitter', async () => {
const { accounts, controller, token, splitter1 , splitter0} = await loadFixture(deployFixture)
await token.transfer(splitter0 , toEther(100));
await controller.removeSplitter(accounts[0])
await expect(controller.removeSplitter(accounts[0])).to.be.revertedWithCustomError(
controller,
'SplitterNotFound()'
)
assert.deepEqual(await controller.getAccounts(), [accounts[1]])
assert.equal(await controller.splitters(accounts[0]), ethers.ZeroAddress)
assert.equal(await splitter1.controller(), controller.target)
assert.equal(await splitter1.lst(), token.target)
assert.deepEqual(await splitter1.getFees(), [
[accounts[7], 2000n],
[accounts[8], 4000n],
])
})

and run with command : npx hardhat test test/core/lst-rewards-splitter.test.ts .

Impact

The owner will not be able to withdraw the reward splitter due to wrong amount provided in withdraw functions.

Tools Used

Manual review

Recommendations

before withdrawal fetch the latest amount available for withdraw.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months 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.