Liquid Staking

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

Optimize splitRewards Function with Unchecked Loop Increment

Summary

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

The proposed change implements an unchecked block for the loop counter increment in the `_splitRewards` function, optimizing gas usage. In Solidity versions 0.8.0 and above, automatic overflow and underflow checks are implemented by default, increasing gas costs for arithmetic operations. By using an unchecked block, we instruct the compiler to skip these checks for the loop counter increment. This optimization is considered safe because the number of iterations is bounded by `fees.length`, which is controlled by the contract owner and is extremely unlikely to approach the maximum value of uint256 (2^256 - 1). The unchecked block is applied only to the loop counter increment, not to any reward or fee calculations.

Vulnerability Details

Original code:

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);
}

Impact

Gas savings, while modest per iteration (approximately 3-5 gas), can accumulate in contracts that are called frequently or have loops that iterate multiple times. For example:\

If `fees.length = 10`, the total savings would be around 30-50 gas.\

If `fees.length = 100`, the total savings would be around 300-500 gas.

Tools Used

Remix IDE Desktop

Recommendations

function _splitRewards(uint256 _rewardsAmount) private {
for (uint256 i = 0; i < fees.length;) {
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);
}
unchecked {
++i;
}
}
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

Support

FAQs

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