Precision loss in _cycleEnd calculation
previewSyncRewards uses integer division then multiplication on the same variable to compute _cycleEnd, losing precision and producing reward cycles that drift from the expected REWARDS_CYCLE_LENGTH interval.
Description
In the previewSyncRewards() function, there is a potential precision loss when calculating the value of _cycleEnd. The calculation uses division and multiplication operations with integers, which can result in a loss of precision due to rounding.
function test_PrecisionLossIn_cycleEnd(uint256 _amount, uint256 _timePassed) public {vm.assume(_amount > 0 && _amount <= 1000 ether);vm.assume(_timePassed > 1 days && _timePassed <= 7 days);uint40 cycleEndBefore = stakedEbtc.__rewardsCycleData().cycleEnd;uint40 lastSyncBefore = stakedEbtc.__rewardsCycleData().lastSync;uint216 rewardCycleAmountBefore = stakedEbtc.__rewardsCycleData().rewardCycleAmount;vm.prank(bob);uint256 shares = stakedEbtc.deposit(_amount, bob);uint256 cycleLength = stakedEbtc.REWARDS_CYCLE_LENGTH();uint256 timeBefore = block.timestamp + _timePassed;// Warp to a time near the end of the cycleuint256 timeNearCycleEnd = timeBefore + cycleLength - (cycleLength / 40);vm.warp(timeNearCycleEnd);uint256 _timestamp = block.timestamp;uint40 cycleEndUnoptimized = (((_timestamp + cycleLength) / cycleLength) * cycleLength).safeCastTo40();uint40 cycleEndOptimized = (((_timestamp + cycleLength))).safeCastTo40();if (cycleEndOptimized - _timestamp < cycleLength / 40) {cycleEndOptimized += cycleLength.safeCastTo40();}if (cycleEndUnoptimized - _timestamp < cycleLength / 40) {cycleEndUnoptimized += cycleLength.safeCastTo40();}stakedEbtc.syncRewardsAndDistribution();assertEq(cycleEndInContract, cycleEndOptimized, "Cycle end should match the calculated value");assertEq(cycleEndUnoptimized, cycleEndOptimized, "Precision loss");}
Console output (extract):
[FAIL: Cycle end should match the calculated value: 1209600 != 1410866;=== Cycle End Comparison ===Contract Cycle End: 1209600Test Optimized Cycle End: 1410866Test Unoptimized Cycle End: 1209600cycleEndUnoptimized != cycleEndOptimized============================ Precision Analysis ===Difference between Optimized and Unoptimized: 201266=========================
Impact
The precision loss in the calculation of _cycleEnd could result in reward cycles that do not perfectly align with the expected time intervals. This leads to small discrepancies in the distribution of rewards over time, as the amount of rewards will be smaller than it is meant to be.
Recommendation
Simplify the formula for calculating _cycleEnd by removing the redundant operation of multiplying and dividing by the same variable REWARDS_CYCLE_LENGTH.
The optimized formula would be:
uint40 _cycleEnd = (_timestamp + REWARDS_CYCLE_LENGTH).safeCastTo40();

