DeFiFoundry
20,000 USDC
View results
Submission Details
Severity: medium
Invalid

Points Rewards Not Settled for Accumulated Points Before setPointsPerEpoch, Leading to Stakeholder Profit Loss

Relevant GitHub Links

setPointsPerEpoch

Summary

In the FjordPoints contract, the setPointsPerEpoch function directly updates pointsPerEpoch, disregarding the points accumulated before this epoch, thereby resulting in lost revenue for stakeholders.

Vulnerability Details

The setPointsPerEpoch code shown below clearly does not account for points accumulated during the current epoch:

function setPointsPerEpoch(uint256 _points) external onlyOwner checkDistribution {
if (_points == 0) {
revert();
}
pointsPerEpoch = _points;
}

Impact

Considering that points are tokens with no emission cap, easily increasing pointsPerEpoch means the previous points devalue, making it more likely to lower pointsPerEpoch. If this function is called late in the epoch, it could severely impact stakeholders' returns. Increasing the rate per epoch without settling previous accruals devalues existing points, negatively affecting stakeholders expecting returns based on the previously announced rate.

Tools Used

Manual Review

Recommendations

Implement special handling for the epoch during which setPointsPerEpoch is called, calculating accumulated points based on elapsed time and handling them accordingly during settlement:

+ bool isSetPointsPerEpoch = false;
+ uint256 pointsChangedEpoch;
function setPointsPerEpoch(uint256 _points) external onlyOwner checkDistribution {
if (_points == 0) {
revert();
}
+ // Divide the current epoch into two parts and calculate accumulated points separately
+ isSetPointsPerEpoch = true;
+ uint256 passedTime = block.timestamp - lastDistribution;
+ pointsChangedEpoch += pointsPerEpoch.mul(passedTime).div(EPOCH_DURATION);
+ pointsChangedEpoch += _points.mul(EPOCH_DURATION - passedTime).div(EPOCH_DURATION);
pointsPerEpoch = _points;
}
function distributePoints() public {
if (block.timestamp < lastDistribution + EPOCH_DURATION) {
return;
}
if (totalStaked == 0) {
return;
}
uint256 weeksPending = (block.timestamp - lastDistribution) / EPOCH_DURATION;
+ // Special handling for the epoch after PointsPerEpoch change
+ if (isSetPointsPerEpoch && weeksPending > 0) {
+ isSetPointsPerEpoch = false;
+ weeksPending -= 0;
+ pointsPerToken = pointsPerToken.add(pointsChangedEpoch);
+ pointsChangedEpoch = 0;
+ }
pointsPerToken =
pointsPerToken.add(weeksPending * (pointsPerEpoch.mul(PRECISION_18).div(totalStaked)));
totalPoints = totalPoints.add(pointsPerEpoch * weeksPending);
lastDistribution = lastDistribution + (weeksPending * 1 weeks);
emit PointsDistributed(pointsPerEpoch, pointsPerToken);
}

By implementing these changes, the contract will fairly account for points during epochs where pointsPerEpoch is changed, thus protecting stakeholder interests and ensuring accurate reward calculations.

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.