QuantAMM

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

Missing per-oracle staleness thresholds lead to incorrect data freshness validation

Summary

The UpdateWeightRunner uses a single staleness threshold for all oracles in a pool, regardless of their update frequencies. This design fails to account for varying oracle hearbeats and will lead to either premature fallbacks or acceptance of unnecessarily stale data.

Vulnerability Details

QuantMMWeightedPool stores a staleness threshold in storage var at init time:

///@dev the maximum amount of time that an oracle an be stale.
uint oracleStalenessThreshold;

UpdateWeightRunner fetches that single threshold value and uses it to check freshness for all oracles in a pool:

function _getData(
address _pool,
bool internalCall
) private view returns (int256[] memory outputData) {
...
//@audit single threshold for all oracles
uint oracleStalenessThreshold = IQuantAMMWeightedPool(_pool).getOracleStalenessThreshold();
for (uint i; i < oracleLength; ) {
OracleData memory oracleResult;
oracleResult = _getOracleData(OracleWrapper(optimisedOracles[i]));
// @audit uses same value for freshness check for every oracle
if (oracleResult.timestamp > block.timestamp - oracleStalenessThreshold) {
outputData[i] = oracleResult.data;
} else {
// Check backups with same threshold
for (uint j = 1; j < numAssetOracles; ) {
// ... backup oracle checks with same threshold
}
}
}
}

The issue with this single per-pool threshold is that different oracles have different heartbeats (time between price updates).

Simple example scenario showing the issue:

  1. Pool on mainnet has 2 assets - ETH and USDC. Chainlink ETH/USD and USDC/USD oracles are used as primary oracles and there are backup oracles configured

  2. ETH/USD oracle heartbeat is 1 hour

  3. USDC/USD oracle heartbeat is 24 hour

Using a single threshold necessarly leads to price issues:

  • if configured threshold is 1 hour, that means USDC/USD oracle prices will be often discarded and unnecessarily backup oracles will be used (which could have the same issue of correct price being discarded)

  • if configured threshold is 24 hours, that means ETH/USD oracle price will be accepted even when stale (up to 23h stale)

This issue will be even more prominent in pools with more than 2 tokens, ie. in pools with 8 tokens using 8 sets of oracles.

Impact

There are multiple negative impacts of this issue:

  • Higher-frequency oracles accept unnecessarily stale (and incorrect) prices

  • Lower-frequency oracles trigger unnecessary fallbacks

  • Increased gas costs from unnecessary oracle fallbacks

I consider this medium risk issue, because it will lead to incorrect prices being used for weight calculations which is the core feature of the protocol.

Tools Used

Manual code review

Recommendations

Staleness thresholds should be configured per-oracle instead of per-pool.

Updates

Lead Judging Commences

n0kto Lead Judge 10 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.

Give us feedback!