QuantAMM

QuantAMM
49,600 OP
View results
Submission Details
Severity: medium
Valid

UpdateWeightRunner::_getData function returns zero price for unapproved oracles

Summary

The UpdateWeightRunner::_getData function can return a price of 0 when the oracle address is not approved, and no backup oracles exist.

Vulnerability Details

The QuantAMM admin can remove an approved oracle address by calling the UpdateWeightRunner::removeOracle function. Therefore, an oracle that was previously approved can later be unapproved.

The UpdateWeightRunner::_getOracleData function returns OracleData(0, 0) when the _oracle address is not an approved oracle.

UpdateWeightRunner::_getOracleData function:

function _getOracleData(OracleWrapper _oracle) private view returns (OracleData memory oracleResult) {
=> if (!approvedOracles[address(_oracle)]) return oracleResult;
(int216 data, uint40 timestamp) = _oracle.getData();
oracleResult.data = data;
oracleResult.timestamp = timestamp;
}

The UpdateWeightRunner::_getData function retrieves price data by calling the UpdateWeightRunner::_getOracleData function. If the optimisedOracles[i] address is unapproved, the returned oracleResult.data and oracleResult.timestamp are 0, failing the staleness checks. When the primary (optimized) oracle fails the checks, the function attempts to use backup oracles. If no backup oracles exist, this section is skipped due to array length checks, and the function proceeds to using the price of 0 from the primary oracle.

UpdateWeightRunner::_getData function:

function _getData(address _pool, bool internalCall) private view returns (int256[] memory outputData) {
...
outputData = new int256[](oracleLength);
uint oracleStalenessThreshold = IQuantAMMWeightedPool(_pool).getOracleStalenessThreshold();
for (uint i; i < oracleLength; ) {
OracleData memory oracleResult;
=> // oracleResult = (0,0) when optimisedOracles[i] is an unapproved address
=> oracleResult = _getOracleData(OracleWrapper(optimisedOracles[i]));
if (oracleResult.timestamp > block.timestamp - oracleStalenessThreshold) {
outputData[i] = oracleResult.data;
} else {
unchecked {
numAssetOracles = poolBackupOracles[_pool][i].length;
}
=> // `for` loop is skipped when no backup oracle
for (uint j = 1 ; j < numAssetOracles; ) {
oracleResult = _getOracleData(
OracleWrapper(poolBackupOracles[_pool][i][j])
);
if (oracleResult.timestamp > block.timestamp - oracleStalenessThreshold) {
break;
} else if (j == numAssetOracles - 1) {
revert("No fresh oracle values available");
}
unchecked {
++j;
}
}
=> // @audit outputData[i] can equal 0
=> outputData[i] = oracleResult.data;
}
unchecked {
++i;
}
}
}

Impact

  • The UpliftOnlyExample contract retrieves price data from the UpdateWeightRunner::getData function to calculate the notional value of LP tokens in USD. This value is used to calculate fees when users remove liquidity. As a result, a price of 0 could lead to incorrect fee calculations.

  • A price of 0 could be used to calculate pool weight updates, leading to incorrect weight adjustments.

Recommendations

Consider reverting in the else block when numAssetOracles == 1:

function _getData(address _pool, bool internalCall) private view returns (int256[] memory outputData) {
...
outputData = new int256[](oracleLength);
uint oracleStalenessThreshold = IQuantAMMWeightedPool(_pool).getOracleStalenessThreshold();
for (uint i; i < oracleLength; ) {
OracleData memory oracleResult;
oracleResult = _getOracleData(OracleWrapper(optimisedOracles[i]));
if (oracleResult.timestamp > block.timestamp - oracleStalenessThreshold) {
outputData[i] = oracleResult.data;
} else {
unchecked {
numAssetOracles = poolBackupOracles[_pool][i].length;
}
+ if (numAssetOracles == 1) {
+ revert("No fresh oracle values available");
+ }
for (uint j = 1 ; j < numAssetOracles; ) {
...
}
outputData[i] = oracleResult.data;
}
unchecked {
++i;
}
}
}
Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_removing_oracle_will_prevent_pools_to_update

Likelihood: Low, when an oracle is removed. Impact: High, Pools using the removed oracle will corrupt the gradient and moving average calculation.

Support

FAQs

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

Give us feedback!