Dria

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

M-1 A malicious validator or generator front-run the fee payments and get more fee than expected

In evm chains every transaction is visible to everyone. Inside LLMOracleCoordinator::respond and LLMOracleCoordinator::validate funtions fees are paid via _increaseAllowance function which it increase the current allowance by the given amount.The problem is a malicious validator or generator can front-run the fee payments and get more fee than expected. More details can be found in the following link: https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit?tab=t.0#heading=h.m9fhqynw2xvt
Since swan team primarly wants to deploy to base L2 chain, this might not be a issue because its an L2 chain. But if they will deploy this an L1 this will be a problem.

Description

Inside the _increaseAllowance function updates the current allowance by the given amount. Lets assume generator has 1000 tokens allowance. When the generator calls the LLMOracleCoordinator::respond function, assume the fee is 100 tokens, if there is no validation coordinator will increase the allowance by 100 tokens.

function respond(uint256 taskId, uint256 nonce, bytes calldata output, bytes calldata metadata)
public
onlyRegistered(LLMOracleKind.Generator)
onlyAtStatus(taskId, TaskStatus.PendingGeneration)
{
TaskRequest storage task = requests[taskId];
for (uint256 i = 0; i < responses[taskId].length; i++) {
if (responses[taskId][i].responder == msg.sender) {
revert AlreadyResponded(taskId, msg.sender);
}
}
assertValidNonce(taskId, task, nonce);
TaskResponse memory response =
TaskResponse({responder: msg.sender, nonce: nonce, output: output, metadata: metadata, score: 0});
responses[taskId].push(response);
emit Response(taskId, msg.sender);
// send rewards to the generator if there is no validation
@> if (task.parameters.numValidations == 0) {
_increaseAllowance(msg.sender, task.generatorFee);
}
// check if we have received enough responses & update task status
bool isCompleted = responses[taskId].length == uint256(task.parameters.numGenerations);
if (isCompleted) {
if (task.parameters.numValidations == 0) {
// no validations required, task is completed
task.status = TaskStatus.Completed;
emit StatusUpdate(taskId, task.protocol, TaskStatus.PendingGeneration, TaskStatus.Completed);
} else {
// now we are waiting for validations
task.status = TaskStatus.PendingValidation;
emit StatusUpdate(taskId, task.protocol, TaskStatus.PendingGeneration, TaskStatus.PendingValidation);
}
}
}

Inside _increaseAllowance will take the current allowance of the coordinator and add the given amount to it which will be 1100 tokens and send to trancaction. Now the malicious generator can front-run this transaction and spend 1000 tokens before allowance is updated. And after allowance transaction passed the malicious generator can spend 1100 tokens again.

function _increaseAllowance(address spender, uint256 amount) internal {
@> feeToken.approve(spender, feeToken.allowance(address(this), spender) + amount);
}

Same issue can be found in LLMOracleCoordinator::validate function. inside internal finalizeValidation function:

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++;
// send validation fee to the validator
@> _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);
}
}
}

Impact

The primary impact is increased fees for malicious generators or validators, allowing them to spend more than intended, leading to a potential drain on contract funds and loss for the protocol. This issue could lead to significant financial loss over time if exploited consistently.

Recommended mitigation

Consider to a fee token use increaseAllowance or safeIncreaseAllowance Functions (ERC20 Extensions)

function _increaseAllowance(address spender, uint256 amount) internal {
feeToken.increaseAllowance(spender, amount);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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