Dria

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

Underflow Vulnerabilities in finalizeValidation

Summary

The finalizeValidation function in the Swan contract includes statistical calculations where unchecked subtractions are performed, specifically in variance and score range checks. The absence of protections against underflows makes these calculations prone to failure, which could cause function reversion and prevent task finalization.

Vulnerability Details

Underflow vulnerabilities in finalizeValidation arise from two primary calculations:

  1. Variance Calculation: In calculating variance, the following line performs a subtraction between each data value and the mean:

    function variance(uint256[] memory data) internal pure returns (uint256 ans, uint256 mean) {
    mean = avg(data);
    uint256 sum = 0;
    for (uint256 i = 0; i < data.length; i++) {
    //@audit possible underflow
    uint256 diff = data[i] - mean;
    sum += diff * diff;
    }

If data[i] is less than mean, an underflow will occur, causing the function to revert. This is particularly likely if validator scores vary widely.

  1. Score Range Check with Standard Deviation: Later, a range check calculates whether scores fall within one standard deviation of the mean, as shown below:

for (uint256 v_i = 0; v_i < task.parameters.numValidations; ++v_i) {
uint256 score = scores[v_i];
//@audit possible underflow
if ((score >= _mean - _stddev) && (score <= _mean + _stddev)) {
// 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++) {
// ignore lower outliers
//@audit possible underflow
if (generationScores[g_i] >= mean - generationDeviationFactor * stddev) {
_increaseAllowance(responses[taskId][g_i].responder, task.generatorFee);
}

It is also possible that std. dev could be higher than mean value.( For scores [1,1,1,1,10]->mean =2.8 ,std= 3.6 ).

Test:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "contracts/llm/LLMOracleCoordinator.sol"; // Adjust the path as necessary
import {LLMOracleRegistry, LLMOracleKind} from "contracts/llm/LLMOracleRegistry.sol"; // Adjust the path as necessary
import {LLMOracleTaskParameters, LLMOracleTask} from "contracts/llm/LLMOracleTask.sol"; // Adjust the path as necessary
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract MockToken is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
contract LLMOracleCoordinatorTest is Test {
LLMOracleCoordinator coordinator;
LLMOracleRegistry registry;
MockToken token;
address dria;
address requester;
address[] generators;
address[] validators;
uint256 taskId;
uint256 constant SUPPLY = 100000 ether;
function setUp() public {
dria = address(this); // Use the test contract as the deployer
requester = address(0x1); // Example address for requester
generators.push(address(0x2)); // Example address for generator 1
generators.push(address(0x3)); // Example address for generator 2
generators.push(address(0x4)); // Example address for generator 3
validators.push(address(0x5)); // Example address for validator 1
validators.push(address(0x6)); // Example address for validator 2
validators.push(address(0x7)); // Example address for validator 2
token = new MockToken("WETH", "WETH");
LLMOracleRegistry impl1 = new LLMOracleRegistry();
ERC1967Proxy proxy = new ERC1967Proxy(address(impl1), "");
registry = LLMOracleRegistry(address(proxy));
LLMOracleCoordinator impl2 = new LLMOracleCoordinator();
ERC1967Proxy proxy2 = new ERC1967Proxy(address(impl2), "");
coordinator = LLMOracleCoordinator(address(proxy2));
registry.initialize(1 ether, 1 ether, address(token));
coordinator.initialize(address(registry), address(token), 0.001 ether, 0.001 ether, 0.001 ether);
for (uint256 i = 0; i < 3; i++) {
token.mint(generators[i], 100 ether);
vm.startPrank(generators[i]);
token.approve(address(registry), 100 ether);
registry.register(LLMOracleKind.Generator);
}
vm.stopPrank();
for (uint256 i = 0; i < 3; i++) {
token.mint(validators[i], 100 ether);
vm.startPrank(validators[i]);
token.approve(address(registry), 100 ether);
registry.register(LLMOracleKind.Validator);
}
vm.stopPrank();
}
function testVarianceUnderflow() public {
token.approve(address(coordinator), 100 ether);
token.mint(address(this), 100 ether);
LLMOracleTaskParameters memory param = LLMOracleTaskParameters({
difficulty: 1,
numGenerations: 2,
numValidations: 2
});
coordinator.request("PROTOCOL", "What is 2 + 2?", "gpt", param);
for (uint256 i = 0; i < 2; i++) {
vm.startPrank(generators[i]);
coordinator.respond(1, 1, "4", "");
}
uint[] memory scores = new uint[]();
scores[0]= 100000;
scores[1]=100000;
vm.startPrank(validators[0]);
coordinator.validate(1, 1111111111, scores, "");
uint[] memory scores2 = new uint[]();
scores[0]= 200000;
scores[1]=200000;
//@audit test fails here because variance calculation underflows
// [FAIL: panic: arithmetic underflow or overflow (0x11)] testShouldRegisterOracles() (gas: 820607)
vm.startPrank(validators[1]);
coordinator.validate(1, 2222222, scores2, "");
}
}

Impact

Underflows in these calculations will cause the finalizeValidation function to revert, which could prevent task finalization.

Tools Used

Manual

Recommendations

Update these calculations to prevent underflows.

function variance(uint256[] memory data) internal pure returns (uint256 ans, uint256 mean) {
mean = avg(data);
uint256 sum = 0;
for (uint256 i = 0; i < data.length; i++) {
- uint256 diff = data[i] - mean;
+ uint256 diff = data[i] >= mean ? data[i] - mean : mean - data[i];
sum += diff * diff;
}
+ uint256 lowerBound = _stddev > _mean ? 0 : _mean - _stddev;
+ uint256 upperBound = _mean + _stddev;
for (uint256 v_i = 0; v_i < task.parameters.numValidations; ++v_i) {
uint256 score = scores[v_i];
- if ((score >= _mean - _stddev) && (score <= _mean + _stddev)) {
+ if ((score >=lowerBound) && (score <= upperBound)) {
- if (generationScores[g_i] >= mean - generationDeviationFactor * stddev) {
+ uint256 lower=mean > ( generationDeviationFactor * stddev)? mean - generationDeviationFactor * stddev:0;
+ if (generationScores[g_i] >=lower) {
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Underflow in computing variance

Underflow in `LLMOracleCoordinator::validate`

Support

FAQs

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