Liquid Staking

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

A malitious actor or splitters owners can cause the fee receivers to lose their new rewards in `LSTRewardsSplitter.sol`

Summary

A malitious actor can cause the fee receivers to lose their new rewards
in LSTRewardsSplitter.sol due to the precision loss in _splitRewards() internal function.

Lines Of Code

https://github.com/Cyfrin/2024-09-stakelink/blob/f5824f9ad67058b24a2c08494e51ddd7efdbb90b/contracts/core/lstRewardsSplitter/LSTRewardsSplitter.sol#L116C1-L125C6
https://github.com/Cyfrin/2024-09-stakelink/blob/f5824f9ad67058b24a2c08494e51ddd7efdbb90b/contracts/core/lstRewardsSplitter/LSTRewardsSplitter.sol#L173C1-L187C6

Vulnerability Details

the splitRewards() external function is callable by anyone,
and _splitRewards() internal function suposed to split the reward among fees receivers.
see the _splitRewards() internal function implementation:

function _splitRewards(uint256 _rewardsAmount) private {
for (uint256 i = 0; i < fees.length; ++i) {
Fee memory fee = fees[i];
uint256 amount = (_rewardsAmount * fee.basisPoints) / 10000;
if (fee.receiver == address(lst)) {
IStakingPool(address(lst)).burn(amount);
} else {
lst.safeTransfer(fee.receiver, amount);
}
}
principalDeposits = lst.balanceOf(address(this));
emit RewardsSplit(_rewardsAmount);
}

given:
_rewardsAmount = 10 .
an array fees[11] each basisPoints = 900.

uint256 amount = (_rewardsAmount * fee.basisPoints) / 10000;

the amount will be 0.
then the rewards will be added to the principalDeposits in the following line:

principalDeposits = lst.balanceOf(address(this));

then it is only withdrawable by the splitter owner.
splitter owner can bypass paying fees.
and it is exploitable by any one.

Impact

loss of fees.

Tools Used

manual review.

Recommendations

there is two solution:
the first is to implement an access control in the splitRewards() external function.
the second is to add a check as follow:

function _splitRewards(uint256 _rewardsAmount) private {
for (uint256 i = 0; i < fees.length; ++i) {
Fee memory fee = fees[i];
uint256 amount = (_rewardsAmount * fee.basisPoints) / 10000;
++ if ((amount * fees.length) < 10000) revert RewardAmountTooSmall();
if (fee.receiver == address(lst)) {
IStakingPool(address(lst)).burn(amount);
} else {
lst.safeTransfer(fee.receiver, amount);
}
}
principalDeposits = lst.balanceOf(address(this));
emit RewardsSplit(_rewardsAmount);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

benterkiii Submitter
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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