Dria

Swan
NFTHardhat
21,000 USDC
View results
Submission Details
Severity: high
Invalid

H-1 Precision Loss in sqrt Function due to Fixed-Point Scaling Mismanagement

Inside Statistics.sol there is a statistic library for uint256 arrays, numbers are treat as fixed-precision floats. Inside this library stddev function responsible to compute the standard deviation of the data. This computes variance, and takes the square root. Inside this square root function there is problem.
This takes a uint256 as input and returns a uint256. But because of decimal deviation the return value will be 9 decimal places rather than 18. This will cause the validators and generators to receive incorrect rewards or none at all.

Description

The LLMOracleCoordinator::finalizeValidation function uses Statistics.stddev to calculate the standard deviation of scores, which is then used to determine if validator and generator scores fall within the "valid" range. Due to the precision loss, the calculated standard deviation is much smaller than expected, often causing validators and generators to receive incorrect scores or none at all.

Inside Statistics::sqrt library devidating x/z will return 1 decimal place because both has 18 decimal places. This will cause the square root function to return a value with 9 decimal place less than the actual value. Because of this will return a so little number compared to other scores which has 18 decimal places validators and generators will receive none or incorrect rewards because of wrong computation.

function sqrt(uint256 x) internal pure returns (uint256 y) {
uint256 z = (x + 1) / 2;
y = x;
while (z < y) {
y = z;
@> z = (x / z + z) / 2;
}
}
function finalizeValidation(uint256 taskId) private {
TaskRequest storage task = requests[taskId];
for (uint256 g_i = 0; g_i < task.parameters.numGenerations; g_i++) {
uint256[] memory scores = new uint256[]();
for (uint256 v_i = 0; v_i < task.parameters.numValidations; v_i++) {
scores[v_i] = validations[taskId][v_i].scores[g_i];
}
// compute the mean and standard deviation
@> (uint256 _stddev, uint256 _mean) = Statistics.stddev(scores);
uint256 innerSum = 0;
uint256 innerCount = 0;
for (uint256 v_i = 0; v_i < task.parameters.numValidations; ++v_i) {
uint256 score = scores[v_i];
@> if ((score >= _mean - _stddev) && (score <= _mean + _stddev)) {
innerSum += score;
innerCount++;
_increaseAllowance(validations[taskId][v_i].validator, task.validatorFee);
}
}
uint256 inner_score = innerCount == 0 ? 0 : innerSum / innerCount;
responses[taskId][g_i].score = inner_score;
}
// now, we have the scores for each generation
// compute stddev for these and pick the ones above a threshold
uint256[] memory generationScores = new uint256[]();
for (uint256 g_i = 0; g_i < task.parameters.numGenerations; g_i++) {
generationScores[g_i] = responses[taskId][g_i].score;
}
// compute the mean and standard deviation
@> (uint256 stddev, uint256 mean) = Statistics.stddev(generationScores);
for (uint256 g_i = 0; g_i < task.parameters.numGenerations; g_i++) {
@> if (generationScores[g_i] >= mean - generationDeviationFactor * stddev) {
_increaseAllowance(responses[taskId][g_i].responder, task.generatorFee);
}
}
}

Impact

Inaccurate Standard Deviations in statistical calculations, causing validators and generators to receive incorrect scores.

Reward Distribution Errors due to misinterpretation of valid scores, potentially penalizing valid results or rewarding incorrect ones.

Protocol Integrity issues as a result of systematic errors in calculations and reward allocation, leading to participant dissatisfaction and potential financial discrepancies.

Proof of Concepts

Put this example in to remix ide. Deploy it locally and call the function with the input of 4e18. You will get the output of 2e9 which is incorrect.It should be 2e18.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract POC {
function sqrt(uint256 x) public pure returns (uint256 y) {
uint256 z = (x + 1) / 2;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
}

Recommended mitigation

Use a div function to avoid the precision loss.

function sqrt(uint256 x) internal pure returns (uint256 y) {
uint256 z = (x + 1) / 2;
y = x;
while (z < y) {
y = z;
-- z = (x / z + z) / 2;
++ z = (div(x,z) + z) / 2;
}
}
+ function div(uint256 x, uint256 y) internal pure returns (uint256) {
+ return x * 1e18 / y;
+ }
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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