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

Precision loss in `FjordPoints::distributePoints` causes stakers to not get their expected amount of points

Summary

The FjordPoints::distributePoints function calculates and distributes points to users based on their staked tokens. This function uses PRECISION_18 to maintain precision during calculation. However, if the total no of staked tokens is too large compared to the PRECISION_18 variable, it can lead to precision loss. This causes stakers to get less than their expected amount of points.

Vulnerability Details

Relevant Link - https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordPoints.sol#L243

The FjordPoints::distributePoints function uses PRECISION_18 to maintain precision during calculation of pointsPerToken. This variable helps ensure that the calculation of points to be distributed is precise. However if the total amount of staked tokens is much higher than PRECISION_18, it will cause a precision loss.

Impact

Stakers get slightly less than their expected amount of points.

Proof of Concept

The impact is demonstrated with the following test, which can be executed with forge test --mt testPointsPrecisionLost.

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.21;
import "forge-std/Test.sol";
import "../src/FjordPoints.sol";
contract FjordPointsTest is Test {
FjordPoints public points;
address public owner;
address public staking;
address public user1;
address public user2;
address public user3;
address public user4;
function setUp() public {
owner = address(this);
staking = address(0x1);
user1 = address(0x2);
user2 = address(0x3);
user3 = address(0x4);
user4 = address(0x5);
points = new FjordPoints();
points.setStakingContract(staking);
}
function testPointsPrecisionLost() public {
// Simulate staking for users
vm.prank(staking);
points.onStaked(user1, 39_457 ether);
vm.prank(staking);
points.onStaked(user2, 862 ether);
vm.prank(staking);
points.onStaked(user3, 23_515 ether);
vm.prank(staking);
points.onStaked(user4, 39_167 ether);
// Move time forward and distribute points
vm.warp(block.timestamp + 1 weeks);
points.distributePoints();
// Users claim points
vm.prank(user1);
points.claimPoints();
vm.prank(user2);
points.claimPoints();
vm.prank(user3);
points.claimPoints();
vm.prank(user4);
points.claimPoints();
uint total = (points.balanceOf(user1) +
points.balanceOf(user2) +
points.balanceOf(user3) +
points.balanceOf(user4));
assertEq(points.balanceOf(user1), 38307395073834234619); // decimal value: 38.307395073834236
assertEq(points.balanceOf(user2), 836885078785642857); // decimal value: 0.8368850787856429
assertEq(points.balanceOf(user3), 22829875438102542693); // decimal value: 22.829875438102544
assertEq(points.balanceOf(user4), 38025844409277579829); // decimal value: 38.02584440927758
// If you add up all the values/decimals, it all adds up to 100
uint256 tokensLost = points.pointsPerEpoch() - total;
require(tokensLost < 1e4, "Tokens are lost");
}
}

This test proves that stakers get less than their expected amount of points.

Tools Used

Manual Review, Foundry

Recommendations

Increase the value of PRECISION_18 to 1e28 or higher to ensure that the new precision variable is always larger than any amount leading to accurate distribution. Also rename PRECISION_18 to reflect the updated value (e,g, PRECISION_28).

After adding this change, you can rerun the poc test provided earlier to verify that the points are being distributed correctly and stakers get their expected points

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.