Dria

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

Outdated `oracleParameters` allows withdrawal with incorrect `minFundAmount` check

Summary

The withdraw() function enforces a minimum fund amount (minFundAmount) in the contract if withdrawals are initiated outside the Withdraw phase. However, changes in the minimum and maximum parameter limits in the LLMOracleManager do not immediately update the oracleParameters in the SwanManager contract, leaving a window where out-of-bounds oracleParameters could still be used.

This allows for an incorrect minFundAmount calculation, potentially letting users withdraw more than they should, especially if the parameters do not meet the new limits.

Vulnerability Details

The withdraw() function has a check to ensure the contract’s balance remains above minFundAmount unless the current phase is Withdraw.

if (phase != Phase.Withdraw) {
if (treasury() < minFundAmount() + _amount) {
revert MinFundSubceeded(_amount);
}
}

The function minFundAmount() computes the required minimum fund amount as follows:

function minFundAmount() public view returns (uint256) {
return amountPerRound + swan.getOracleFee();
}

This depends on the result of swan.getOracleFee(), which in turn relies on oracleParameters.

The getOracleFee() function in Swan calculates the oracle fee using coordinator.getFee(oracleParameters).

function getOracleFee() external view returns (uint256) {
(uint256 totalFee,,) = coordinator.getFee(oracleParameters);
return totalFee;
}

Also, the protocol enforces that oracleParameters should fall within certain set bounds of min/max which can also be modified via the LLMOracleManager:setParameters()

function setParameters(LLMOracleTaskParameters calldata minimums, LLMOracleTaskParameters calldata maximums)
public
onlyOwner
{
minimumParameters = minimums;
maximumParameters = maximums;
}

Therefore, if the parameter limits are modified in LLMOracleManager without an immediate update of oracleParameters in SwanManager, coordinator.getFee() will still use the outdated values, which are possibly out of the newly set bounds.

Flawed getFee() Calculation:

The coordinator.getFee() function uses oracleParameters without checking if its values are within the new parameter limits.

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)));
}

In other parts of the codebase, the protocol relies on onlyValidParameters() modifier to perform these validations as follows:

modifier onlyValidParameters(LLMOracleTaskParameters calldata parameters) {
if (
parameters.difficulty < minimumParameters.difficulty || parameters.difficulty > maximumParameters.difficulty
) {
revert InvalidParameterRange(
parameters.difficulty, minimumParameters.difficulty, maximumParameters.difficulty
);
}
if (
parameters.numGenerations < minimumParameters.numGenerations
|| parameters.numGenerations > maximumParameters.numGenerations
) {
revert InvalidParameterRange(
parameters.numGenerations, minimumParameters.numGenerations, maximumParameters.numGenerations
);
}
if (
parameters.numValidations < minimumParameters.numValidations
|| parameters.numValidations > maximumParameters.numValidations
) {
revert InvalidParameterRange(
parameters.numValidations, minimumParameters.numValidations, maximumParameters.numValidations
);
}
_;
}

However, the getFee() function does not use this modifier. Since parameters.difficulty, parameters.numGenerations, and parameters.numValidations are not validated, they could exceed the new limits if not immediately updated.

Impact

The time window between updating parameter limits in LLMOracleManager and updating oracleParameters in SwanManager allows outdated values, possibly out of range, to be used in calculating minFundAmount. This could result in an inaccurate minimum fund amount, allowing users to withdraw more funds than intended when the contract is not in the Withdraw phase, potentially depleting the treasury below acceptable levels.

Tools Used

Manual Review

Recommendations

Add the onlyValidParameters modifier to coordinator.getFee() to enforce parameter limits:

function getFee(LLMOracleTaskParameters calldata parameters)
public
view
+ onlyValidParameters(parameters) // Enforce parameter validation
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)));
}

This ensures that parameters.difficulty, parameters.numGenerations, and parameters.numValidations are validated consistently. This way, even if the owner made changes to the limits in LLMOracleManager but has not yet updated oracleParameters in SwanManager, the newly set limits will be in effect immediately blocking any potential out-of-bounds transactions.

Updates

Lead Judging Commences

inallhonesty Lead Judge
12 months ago
inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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