QuantAMM

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

Inadequet Loop Logic in Backup Oracles - Single Failure Disrupt Whole Process

Title

Inadequet Loop Logic in Backup Oracles - Single Failure Disrupt Whole Process

Summary

_getData function in UpdateWeightRunner contract is used to update asset weights in a pool using oracle data.
However, it has a critical flaw in its looping backup oracles: if any single asset's oracle fails, the whole update process would fail.

Vulnerability Details

Here's the implementation of _getData function in UpdateWeightRunner contract:

function _getData(address _pool, bool internalCall) private view returns (int256[] memory outputData) {
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();
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;
}
}
}

As seen above, the UpdateWeightRunner contract relies on multiple oracles (primary and backups) to fetch data for each asset. If any asset's oracles fail to provide fresh data, the contract reverts the entire weight update process.

This means that one problematic asset oracle can prevent updates for all assets in the pool, even if other oracles are functioning correctly. The design does not allow for partial updates or fallback mechanisms, creating a systemic point of failure.

Impact

Stale updates occur when a single failing oracle blocks weight updates for all assets, leading to outdated pool data and reduced efficiency.

This creates an exploitation risk, where attackers can manipulate predictable price discrepancies for profit, harming liquidity providers and the protocol.

Tools Used

Manual Review

Recommendations

Implement fallback mechanisms like using the last successful values or allowing partial updates based on risk thresholds to ensure the pool remains operational during oracle failures.

Updates

Lead Judging Commences

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

finding_no_try_catch_for_Chainlink_oracle

Likelihood: Low, price feed should revert. Impact: High, DoS of the protocol

Support

FAQs

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