QuantAMM

QuantAMM
49,600 OP
View results
Submission Details
Severity: low
Invalid

Single Staleness Threshold in quantAMM Pools Risks Incorrect Oracle Data Handling

Summary

The protocol currently enforces a uniform staleness threshold across all oracles within a pool. This approach neglects the varying update intervals of different oracles. By treating all oracles as if they have the same update frequency, the system risks either prematurely invalidating fresh data or accepting stale information.

Vulnerability Details

getOracleStalenessThreshold returns the oracleStalenessThreshold of the pool:

///@dev the maximum amount of time that an oracle an be stale.
uint oracleStalenessThreshold;
function getOracleStalenessThreshold() external view override returns (uint) {
return oracleStalenessThreshold;
}

It is used in the _getData function of updateWeightRunner to check for stale prices of assets:

function _getData(address _pool, bool internalCall) private view returns (int256[] memory outputData) {//@audit-ok
require(internalCall || (approvedPoolActions[_pool] & MASK_POOL_GET_DATA > 0), "Not allowed to get data");
//optimised == happy path, optimised into a different array to save gas
address[] memory optimisedOracles = poolOracles[_pool];
uint oracleLength = optimisedOracles.length;
uint numAssetOracles;
outputData = new int256[](oracleLength);
uint oracleStalenessThreshold = IQuantAMMWeightedPool(_pool).getOracleStalenessThreshold();//@audit-issue staleness threshold
for (uint i; i < oracleLength; ) {
// Asset is base asset
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;
}
for (uint j = 1 /*0 already done via optimised poolOracles*/; j < numAssetOracles; ) {
oracleResult = _getOracleData(
// poolBackupOracles[_pool][asset][oracle]
OracleWrapper(poolBackupOracles[_pool][i][j])
);
if (oracleResult.timestamp > block.timestamp - oracleStalenessThreshold) {
// Oracle has fresh values
break;
} else if (j == numAssetOracles - 1) {
// All oracle results for this data point are stale. Should rarely happen in practice with proper backup oracles.
revert("No fresh oracle values available");
}
unchecked {
++j;
}
}
outputData[i] = oracleResult.data;
}
unchecked {
++i;
}
}
}

The issue is that each pool uses a single staleness threshold to determine whether oracle data is valid. But different asset oracles within a pool can have different heartbeats.

Impact

It can cause stale data being incorrectly flagged as valid or valid data being rejected as stale, depending on the heartbeat of the oracle.

Tools Used

VSCode

Recommendations

Replace the single threshold with per-oracle configurations.

Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

invalid_oracle_same_threshold_for_assets_in_pool

This is by design, staleness is a strategy aspect: it requires all data to have been updated within n minutes. No more precision needed.

Support

FAQs

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