Dria

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

`LLMOracleManager:getFee` incorrect bitwise operation scales `generationFee` and `validationFee` excessively

Summary

LLMOracleManager:getFee calculates totalFee, generatorFee and validatorFee. An incorrect bitwise operation scales generationFee and validationFee by $2^{\text{difficulty + 1}}$ leading to excessive fee escalation, even when the task difficulty is 0.

Vulnerability Details

As the documentation states, the LLM Oracle nodes listen to LLMOracleCoordinator to wait for generation and validation requests.

Once a request is received, they do a proof-of-work via a cryptographic hash function that results in a sufficiently small digest. How small this target value is expected to be is determined by the difficulty value of the task. The target value for the proof-of-work is computed by:

given a taskId and a nonce, LLMOracleCoordinator:assertValidNonce function checks that proof-of-work is valid:

https://github.com/Cyfrin/2024-10-swan-dria/blob/main/contracts/llm/LLMOracleManager.sol#L110-L120

function assertValidNonce(uint256 taskId, TaskRequest storage task, uint256 nonce) internal view {
bytes memory message = abi.encodePacked(taskId, task.input, task.requester, msg.sender, nonce);
@> if (uint256(keccak256(message)) > type(uint256).max >> uint256(task.parameters.difficulty)) {
revert InvalidNonce(taskId, nonce);
}
}

The following table shows how the target diminishes exponentially with the difficulty

Difficulty Target Target Reduction Factor
0 115792089237316195423570985008687907853269984665640564039457584007913129639935 1
1 57896044618658097711785492504343953926634992332820282019728792003956564819967 2
2 28948022309329048855892746252171976963317496166410141009864396001978282409983 4
3 14474011154664524427946373126085988481658748083205070504932198000989141204991 8
4 7237005577332262213973186563042994240829374041602535252466099000494570602495 16
5 3618502788666131106986593281521497120414687020801267626233049500247285301247 32
6 1809251394333065553493296640760748560207343510400633813116524750123642650623 64
7 904625697166532776746648320380374280103671755200316906558262375061821325311 128
8 452312848583266388373324160190187140051835877600158453279131187530910662655 256
9 226156424291633194186662080095093570025917938800079226639565593765455331327 512
10 113078212145816597093331040047546785012958969400039613319782796882727665663 1024

The following table shows how the fee scales with the difficulty using the LLMOracleManager:getFee function. To demonstrate the fee scaling,
generationFee and validationFee are set to 1 in the LLMOracleCoordinator contract.

Difficulty Generator Fee Validator Fee
0 2 2
1 4 4
2 8 8
3 16 16
4 32 32
5 64 64
6 128 128
7 256 256
8 512 512
9 1024 1024
10 2048 2048

Comparing the two tables, it is observed that the fees are scaling by $2^{\text{difficulty + 1}}$ instead of $2^{\text{difficulty}}$. This escalates the fees when the difficulty is 0.

This escalation is a result of the bitwise operation in the getFee function:

https://github.com/Cyfrin/2024-10-swan-dria/blob/main/contracts/llm/LLMOracleManager.sol#L110-L120

function getFee(LLMOracleTaskParameters calldata parameters)
public
view
returns (uint256 totalFee, uint256 generatorFee, uint256 validatorFee)
{
@> uint256 diff = (2 << uint256(parameters.difficulty));
generatorFee = diff * generationFee;
validatorFee = diff * validationFee;
totalFee =
platformFee + (parameters.numGenerations * (generatorFee + (parameters.numValidations * validatorFee)));
}

PoC

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console} from "../lib/forge-std/src/Test.sol";
import {LLMOracleManager} from "../contracts/llm/LLMOracleManager.sol";
import {LLMOracleCoordinator} from "../contracts/llm/LLMOracleCoordinator.sol";
import {LLMOracleTaskParameters} from "../contracts/llm/LLMOracleTask.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract LLMOracleManagerCoordinator is Test {
LLMOracleCoordinator coordinatorImplementation;
address proxy;
LLMOracleCoordinator coordinator;
function setUp() public {
coordinatorImplementation = new LLMOracleCoordinator();
// Initialize the coordinator implementation contract
proxy = address(
new ERC1967Proxy(
address(coordinatorImplementation),
abi.encodeWithSignature(
"initialize(address,address,uint256,uint256,uint256)",
address(0x1), // _oracleRegistry is not used for this test
address(0x2), // _feeToken is not used for this test
1, // _platformFee
1, // _generationFee
1 // _validationFee
)
)
);
coordinator = LLMOracleCoordinator((proxy));
}
// Check that the coordinator is initialized with the correct fees
function testInitialized() public view{
assertEq(coordinator.platformFee(), 1);
assertEq(coordinator.generationFee(), 1);
assertEq(coordinator.validationFee(), 1);
}
// Get generatorFee and validatorFee for a task with difficulty 0, 1 generation, and 1 validation
function testGetFee() public view {
LLMOracleTaskParameters memory taskParameters = LLMOracleTaskParameters({
difficulty: 0, // 0 difficulty is unlikely to be used in practice, but it's used to demonstrate the fee scaling
numGenerations: 1,
numValidations: 1
});
(, uint256 generatorFee, uint256 validatorFee) = coordinator.getFee(taskParameters);
console.log("generatorFee: %d", generatorFee);
console.log("validatorFee: %d", validatorFee);
assertEq(generatorFee, 2); // Fee scaled at difficulty 0
}
// Helper function to calculate the target for a given difficulty
function calculateTarget(uint256 difficulty) public pure returns (uint256) {
require(difficulty <= 256, "Difficulty exceeds bounds");
return type(uint256).max >> uint256(difficulty);
}
// Helper function to log the fees for difficulties 0-10
function testLogFees() public view{
for (uint8 i = 0; i <= 10; i++) {
LLMOracleTaskParameters memory taskParameters = LLMOracleTaskParameters({
difficulty: i,
numGenerations: 1,
numValidations: 1
});
(,uint256 generatorFee, uint256 validatorFee) = coordinator.getFee(taskParameters);
console.log("Difficulty: %d, generatorFee: %d, validatorFee: %d", i, generatorFee, validatorFee);
}
}
// Helper function to log the targets for difficulties 0-10
function testLogTargets() public pure{
for (uint256 i = 0; i <= 10; i++) {
console.log("Difficulty: %d, Target: %d", i, calculateTarget(i));
}
}
}

Steps to reproduce:

Inside the protocol directory:

  1. Run npm i --save-dev @nomicfoundation/hardhat-foundry to install the hardhat-foundry plugin.

  2. Add require("@nomicfoundation/hardhat-foundry"); to the top of the hardhat.config.js file.

  3. Run npx hardhat init-foundry in the terminal. This will generate a foundry.toml file based on the Hardhat project’s existing configuration, and will install the forge-std library.

  4. Create a file ("LLMOracleCoordinatorTest.t.sol") in ./test/ directory.

  5. Copy the provided test case into the file.

Impact

Excessive fees are charged for generation and validation tasks

Tools Used

Manual review

Recommendations

Change the getFee function to scale the fees by $2^{\text{difficulty}}$ instead of $2^{\text{difficulty + 1}}$

@@ -112,7 +112,7 @@ contract LLMOracleManager is OwnableUpgradeable {
view
returns (uint256 totalFee, uint256 generatorFee, uint256 validatorFee)
{
- uint256 diff = (2 << uint256(parameters.difficulty));
+ uint256 diff = (1 << uint256(parameters.difficulty));
generatorFee = diff * generationFee;
validatorFee = diff * validationFee;
totalFee =

With this change, fees are scaled according to the target reduction factor, and the fees are not scaled when the difficulty is 0.

After the change, the fees will scale as follows:

generationFee and validationFee are set to 1 in the LLMOracleCoordinator contract to demonstrate the fee scaling.

Difficulty Generator Fee Validator Fee
0 1 1
1 2 2
2 4 4
3 8 8
4 16 16
5 32 32
6 64 64
7 128 128
8 256 256
9 512 512
10 1024 1024

note that there is no scaling when the difficulty is 0.

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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