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);
@> if (task.parameters.numValidations == 0) {
_increaseAllowance(msg.sender, task.generatorFee);
}
bool isCompleted = responses[taskId].length == uint256(task.parameters.numGenerations);
if (isCompleted) {
if (task.parameters.numValidations == 0) {
task.status = TaskStatus.Completed;
emit StatusUpdate(taskId, task.protocol, TaskStatus.PendingGeneration, TaskStatus.Completed);
} else {
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++;
@> _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);
}