The removeSplitter function in the LSTRewardsSplitterController
fails to account for rewards already distributed when calculating the balance to withdraw. This miscalculation results in a potential underflow error during the withdrawal process, leading to an unexpected contract revert. Specifically, the issue arises when the entire balance of the splitter is passed to the withdraw function without considering the portion of the balance already distributed as rewards through the splitRewards function.
In the removeSplitter function, the function incorrectly calculates the balance passed to the withdraw function. The balance includes both the splitter's principal deposits and the rewards distributed to various accounts. After rewards are split, the actual balance of the splitter decreases, but the withdraw function still attempts to withdraw the full, original balance, which causes an underflow in the principalDeposits
.
In the removeSplitter
function, we track the value of LST tokens (the balance of a splitter) in the balance
variable and the principalDeposits
of a specific splitter. If the values of balance
and principalDeposits
are not equal—possibly due to a direct transfer of LST tokens to the splitter without using the deposit
function—we split the rewards. In the splitRewards function, some LST tokens are transferred as fee amounts to the fee.receiver
, and the principalDeposits state variable is updated to reflect the actual balance of LST tokens.
Thus, the value of LST tokens is now different. However, we still pass the cached balance
variable—which doesn't reflect the updated LST token amount of the splitter—to the withdraw
function. This leads to an underflow error because the principalDeposits is less than _amount
, or the total balance of LST tokens, causing an unexpected revert.
Alice, a user of the protocol, has deposited 1,000 LST into her splitter contract, managed by the protocol. This 1,000 LST is the principal deposit. Over time, Alice earns rewards of 100 LST, bringing the total balance of her splitter to 1,100 LST.
Principal Deposits: 1,000 LST
Accumulated Rewards: 100 LST
Total Balance: 1,100 LST
At some point, the protocol decides to remove Alice’s splitter contract. The controller contract calls the removeSplitter
function to:
Withdraw the entire balance from the splitter.
Transfer the funds back to Alice’s wallet.
Before calling withdraw
, the removeSplitter
function checks whether the balance and the principal deposits match. Since the balance is 1,100 LST and the principal is 1,000 LST, the contract calls splitRewards
to distribute the 100 LST rewards to the fee receivers.
After splitRewards
is called, the protocol proceeds to withdraw the balance from Alice’s splitter:
The balance is still 1,100 LST, but the principal deposits are now reduced due to the rewards being split.
If the principalDeposits
is reduced to a value less than 1,100 LST (perhaps 1,000 LST or lower), calling withdraw(1,100)
will underflow when the contract tries to decrease principalDeposits
:
This causes the contract to revert, preventing Alice from withdrawing her funds and completing the removal of the splitter.
Due to this bug:
Alice’s withdrawal fails, leaving her funds locked in the splitter.
The protocol is unable to remove Alice’s splitter contract, and the funds are stuck.
The improper handling of the balance during the removeSplitter
function can result in user funds being locked within the splitter contract. If the Controller incorrectly withdraws the full balance (including rewards) without adjusting for the distributed rewards, it triggers an underflow error and unable to remove Splitters.
Manual Review
After calling the splitRewards function, update the cached balance variable to correctly account for the changes in principal deposits, preventing underflows during withdrawals.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.