QuantAMM

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

Using a single threshold for oracle staleness can lead to unintended behavior.

Summary

When fetching prices from different oracles in a pool, the protocol uses a single threshold timeout to check for oracle staleness. This can lead to unintended behavior, as Chainlink oracles have different refresh intervals.

Vulnerability Details

The WeightedPool can contain different assets, so we need to set a separate oracle for each asset, as illustrated below.

contracts/UpdateWeightRunner.sol:240
240: for (uint i; i < _poolSettings.oracles.length; ++i) {
241: require(_poolSettings.oracles[i].length > 0, "Empty oracles array");
242: for (uint j; j < _poolSettings.oracles[i].length; ++j) {
243: if (!approvedOracles[_poolSettings.oracles[i][j]]) {
244: revert("Not approved oracled used");
245: }
246: }
247: }
248:

Here, we set all the oracles for assets in the pool. Let’s take a look at where we define the oracleStalenessThreshold to check for oracle timeouts or stale prices.

contracts/QuantAMMWeightedPool.sol:694
694: function initialize(
695: int256[] memory _initialWeights,
696: PoolSettings memory _poolSettings,
697: int256[] memory _initialMovingAverages,
698: int256[] memory _initialIntermediateValues,
699: uint _oracleStalenessThreshold
700: ) public initializer {
701: require(_poolSettings.assets.length > 0 && _poolSettings.assets.length == _initialWeights.length, "INVASSWEIG"); //Invalid assets / weights array
702:
703: assets = _poolSettings.assets;
704: poolSettings.assets = new address[](_poolSettings.assets.length);
705: for (uint i; i < _poolSettings.assets.length; ) {
706: poolSettings.assets[i] = address(_poolSettings.assets[i]);
707: unchecked {
708: ++i;
709: }
710: }
711:
712: oracleStalenessThreshold = _oracleStalenessThreshold;
713: updateInterval = _poolSettings.updateInterval;

At line 712, a single threshold timeout is set for all oracles to check for staleness when fetching price data.

  • If Oracle A has a refresh time of 1 day and Oracle B has a refresh time of 1 hour, it becomes unclear which threshold to set.

  • Setting the timeout to 1 day risks treating stale prices from Oracle B as the latest prices.

  • Conversely, setting the timeout to 1 hour could lead to unnecessary reverts for Oracle A, even when its price is still valid.

Code snippet where we check this timeout:

uint oracleStalenessThreshold = IQuantAMMWeightedPool(_pool).getOracleStalenessThreshold();// @audit : is stalenees threshold same for all oracle ?
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");
}

Impact

In a pool with multiple assets, using a single threshold to check oracle staleness poses challenges. If the timeout is set too high, stale prices might be wrongly accepted as the latest values. On the other hand, setting the timeout too low could result in valid prices being incorrectly rejected.

Tools Used

Manual Review

Recommendations

This issue can be resolved by setting a specific threshold for each oracle based on the staleness period defined by the Chainlink oracle.

Updates

Lead Judging Commences

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

Appeal created

0xaman Submitter
11 months ago
n0kto Lead Judge
11 months ago
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!