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

`claimPoints` vulnerability - Users can claim unaccountable points

Summary

The FjordPoints contract has a vulnerability in the claimPoints function, enabling users to accumulate an unfair amount of points by staking and unstaking within the same epoch. This exploitation becomes feasible once at least one epoch has elapsed since the last point distribution.

Vulnerability Details

In FjordPoints, the distributePoints function is invoked through the checkDistribution modifier, which ensures points are only distributed if enough time has elapsed:

function distributePoints() public {
if (block.timestamp < lastDistribution + EPOCH_DURATION) {
return;
}
if (totalStaked == 0) {
return;
}
uint256 weeksPending = (block.timestamp - lastDistribution) / EPOCH_DURATION;
pointsPerToken =
pointsPerToken.add(weeksPending * (pointsPerEpoch.mul(PRECISION_18).div(totalStaked)));
totalPoints = totalPoints.add(pointsPerEpoch * weeksPending);
lastDistribution = lastDistribution + (weeksPending * 1 weeks);
emit PointsDistributed(pointsPerEpoch, pointsPerToken);
}

The claimPoints function in FjordPoints allows users to claim their pending points:

function claimPoints() external checkDistribution updatePendingPoints(msg.sender) {
UserInfo storage userInfo = users[msg.sender];
uint256 pointsToClaim = userInfo.pendingPoints;
if (pointsToClaim > 0) {
userInfo.pendingPoints = 0;
_mint(msg.sender, pointsToClaim);
emit PointsClaimed(msg.sender, pointsToClaim);
}
}

Here's a scenario that user can accumulate points for the entire epoch despite having tokens staked for just a brief period:

  1. Wait for a period just after an epoch boundary after lastDistribution.

  2. User stake a specific amount of tokens.

  3. Call point distribution and claim points

  4. Unstake within the same epoch.

Impact

Users can earn points disproportionate to their actual staking time, undermining the incentive structure meant to reward long-term stakers. This can result in an unfair distribution of points, potentially devaluing them for honest stakers and leading to centralization of power or economic advantages if points carry governance or utility value.

Tools Used

Manual Review

Recommendations

Call distributePoints before users claim points.

function claimPoints() external checkDistribution updatePendingPoints(msg.sender) {
+ distributePoints();
UserInfo storage userInfo = users[msg.sender];
uint256 pointsToClaim = userInfo.pendingPoints;
if (pointsToClaim > 0) {
userInfo.pendingPoints = 0;
_mint(msg.sender, pointsToClaim);
emit PointsClaimed(msg.sender, pointsToClaim);
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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