Vulnerability Details
When request LLM generation, fee is calculated, transfer and linked with taskId as below:
function request(
bytes32 protocol,
bytes memory input,
bytes memory models,
LLMOracleTaskParameters calldata parameters
) public onlyValidParameters(parameters) returns (uint256) {
(uint256 totalfee, uint256 generatorFee, uint256 validatorFee) = getFee(parameters);
. . . . . .
feeToken.transferFrom(msg.sender, address(this), totalfee);
. . . . . .
requests[taskId] = TaskRequest({
requester: msg.sender,
protocol: protocol,
input: input,
parameters: parameters,
status: TaskStatus.PendingGeneration,
generatorFee: generatorFee,
validatorFee: validatorFee,
platformFee: platformFee,
models: models
});
}
getFee()
function:
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)));
}
When request choose to have validator, when finalize validation, some of validator and generator wont be rewarded due to score conditions:
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];
}
(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;
}
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;
}
(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);
}
}
}
These tokens are not transfered back to the requester and stay in the contract
Impact
User are not able to get unused tokens back.
Tools Used
Manual review
Recommendations
Transfer back unused tokens for user.