Liquid Staking

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

Gas Limit Vulnerability in LSTRewardsSplitter.sol

Summary

The LSTRewardsSplitter contract has a vulnerability in the splitRewards function that can cause the transaction to revert when there are a large number of fees configured. From the loop in the _splitRewards function, which iterates over all the fees and transfers the reward amounts. If the total gas consumed by the loop exceeds the gas limit, the transaction will revert, and the reward distribution will fail.

Vulnerability Details

The issue is in the _splitRewards function, which is called by splitRewards. The _splitRewards function iterates over all the fees stored in the fees array and transfers the reward amounts to the respective fee receivers using lst.safeTransfer. If there are a significant number of fees, the loop will execute many iterations, consuming gas for each calculation and token transfer. If the total gas consumed exceeds the gas limit of the transaction, the transaction will revert, and the reward distribution will fail.

LSTRewardsSplitter.sol# https://github.com/Cyfrin/2024-09-stakelink/blob/f5824f9ad67058b24a2c08494e51ddd7efdbb90b/contracts/core/lstRewardsSplitter/LSTRewardsSplitter.sol#L173-L183

function _splitRewards(uint256 _rewardsAmount) private {
for (uint256 i = 0; i < fees.length; ++i) { // <-- Vuln: Loop over all fees
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); // <-- Vuln: Token transfer for each fee
}
}
principalDeposits = lst.balanceOf(address(this));
emit RewardsSplit(_rewardsAmount);
}
  1. The splitRewards function is called to distribute the rewards among the fee receivers.

  2. Inside the splitRewards function, the _splitRewards function is invoked with the calculated reward amount.

  3. The _splitRewards function contains a loop that iterates over all the fees stored in the fees array.

  4. For each fee, the function calculates the reward amount based on the fee's basis points and transfers the amount to the fee receiver using lst.safeTransfer.

  5. If there are a large number of fees configured, the loop will execute many iterations, and each iteration will consume gas for the calculations and token transfers.

  6. If the total gas consumed by the loop exceeds the gas limit of the transaction, the transaction will revert, and the reward distribution will fail.

Impact

This bug affects users because if the splitRewards function fails due to exceeding the gas limit, the rewards will not be distributed to the fee receivers as intended. Users who are expecting to receive their share of the rewards will not receive them.

Tools Used

Vs Code

Recommendations

Instead of processing all fees in a single transaction, break them down into smaller batches that can be processed within the gas limit.

function _splitRewards(uint256 _rewardsAmount) private {
+ uint256 batchSize = 50; // Adjust the batch size as needed
+ for (uint256 i = 0; i < fees.length; i += batchSize) {
+ for (uint256 j = i; j < i + batchSize && j < fees.length; ++j) {
Fee memory fee = fees[j];
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);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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