Sparkn

CodeFox Inc.
DeFiFoundryProxy
15,000 USDC
View results
Submission Details
Severity: low
Valid

Precision loss/Rounding to Zero in `_distribute()`

Summary

The identified vulnerability is associated with the _distribute function in
Distributor.sol. In scenarios where the total token amount is low or low values are used for percentages, the function may encounter a precision issue.
This arises due to the division of totalAmount by BASIS_POINTS to calculate the distribution amount for each winner. The precision error can lead to incorrect token distribution, affecting the fairness and accuracy of rewards to winners.

Vulnerability Details

The vulnerability stems from the calculation of amount within the distribution loop. The formula amount = totalAmount * percentages[i] / BASIS_POINTS involves a division operation that could result in loss of precision(Rounding to Zero) when dealing with small totalAmount values or low percentages[i]. This imprecision can lead to token amounts being rounded down to zero, resulting in unfair or incomplete rewards for winners.

Proof Of Concept:

To simulate the vulnerability we need to make changes in the modifier
setUpContestForJasonAndSentJpycv2Token:

Code:

modifier setUpContestForJasonAndSentJpycv2Token(address _organizer) {
vm.startPrank(factoryAdmin);
bytes32 randomId = keccak256(abi.encode("Jason", "001"));
proxyFactory.setContest(_organizer, randomId, block.timestamp + 8 days, address(distributor));
vm.stopPrank();
bytes32 salt = keccak256(abi.encode(_organizer, randomId, address(distributor)));
address proxyAddress = proxyFactory.getProxyAddress(salt, address(distributor));
vm.startPrank(sponsor);
MockERC20(jpycv2Address).transfer(proxyAddress, 10);
vm.stopPrank();
// console.log(MockERC20(jpycv2Address).balanceOf(proxyAddress));
// assertEq(MockERC20(jpycv2Address).balanceOf(proxyAddress), 10000 ether);
_;
}

We change the value transferred to the proxyAddress to 10 tokens.

Note: We are not using 10 ether here as ether has 18 decimals which misguides the intended attack.

Now, we create a function called testPrecisionLoss() wherein we simulate end of a contest and call the deployProxyAndDistribute() function. This makes use of the modified createData() function to send in 95 winners, each being rewarded with percentage of 100 BASIS POINTS, which is equal to (10000 - COMMISSION_FEE) i.e. 9500 BASIS POINTS thus satisfying the conditions in the _distribute() function and allowing distribution of funds.

Code:

function testPrecisionLoss() public setUpContestForJasonAndSentJpycv2Token(organizer) {
bytes32 randomId_ = keccak256(abi.encode("Jason", "001"));
//create data from modified createData() function
bytes memory data = createData();
vm.startPrank(organizer);
console.log("User1 Start Balance -", MockERC20(jpycv2Address).balanceOf(user1));
console.log("Stadium Balance Before: ",MockERC20(jpycv2Address).balanceOf(stadiumAddress));
//warping to the time where contest ends and token distribution is allowed
vm.warp(30 days);
// distributing the rewards to all 95 winners
proxyFactory.deployProxyAndDistribute(randomId_, address(distributor), data);
console.log("Stadium End Balance: ",MockERC20(jpycv2Address).balanceOf(stadiumAddress));
console.log("User1 After Balance -", MockERC20(jpycv2Address).balanceOf(user1));
vm.stopPrank();
}

The logs prove the existence of precision loss:

Running 1 test for test/integration/ProxyFactoryTest.t.sol:ProxyFactoryTest
[PASS] testPrecisionLoss() (gas: 892788)
Logs:
0x000000000000000000000000000000000000000E
User1 Start Balance - 0
Stadium Balance Before: 0
Stadium End Balance: 10
User1 After Balance - 0
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.16ms

As we can see, all of the balance gets transferred to the stadiumAddress from the contract as the _commissionTransfer() function declares:

function _commissionTransfer(IERC20 token) internal {
token.safeTransfer(STADIUM_ADDRESS, token.balanceOf(address(this)));
}

Due to the precision loss(Rounding to Zero), none of the amount gets transferred to any winners and thus, all the "remaining tokens" get sent to the stadiumAddress, thus leaving our winners - in this case, user1 - rewarded with 0 balance.

Impact

The Rounding to Zero vulnerability has the potential to undermine the intended fairness and accuracy of the reward distribution process. In scenarios where the token balance is very small or percentages are low, the distribution algorithm could yield incorrect or negligible rewards to winners. This impacts the trust and credibility of the protocol, potentially leading to user dissatisfaction and decreased participation.

Tools Used

Manual Review
Foundry
VSCode

Recommendations

Consider instituting a predefined minimum threshold for the percentage amount used in the token distribution calculation. This approach ensures that the calculated distribution amount maintains a reasonable and equitable value, even when dealing with low percentages.

Additionally, an alternative strategy involves adopting a rounding up equation that consistently rounds the distribution amount upward to the nearest integer value.

By incorporating either of these methodologies, the precision vulnerability associated with small values and percentages can be effectively mitigated, resulting in more accurate and reliable token distribution outcomes.

Support

FAQs

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