Vulnerability Details
In _getData() function in UpdateWeightRunner contract, it use _getOracleData() function to get data from oracle:
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");
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; ) {
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 ; 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;
}
}
outputData[i] = oracleResult.data;
}
unchecked {
++i;
}
}
}
It will call to getData() function of oracle:
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;
}
getData() function in OracleWrapper abstract contract:
function getData() public view returns (int216 data, uint40 timestamp) {
(data, timestamp) = _getData();
require(timestamp > 0, "INVORCLVAL");
}
_getData() function in ChainlinkOracle contract:
function _getData() internal view override returns (int216, uint40) {
(, int data, , uint timestamp, ) =
priceFeed.latestRoundData();
require(data > 0, "INVLDDATA");
data = data * int(10 ** normalizationFactor);
return (int216(data), uint40(timestamp));
}
It can be seen that there are 2 scenario that will lead to revert in getData() and _getData() function, one of them can be happened in the real scenario, which is noted by chainlink's dev at here link
*
* @dev A timestamp with zero value means the round is not complete and should not be used.
*/
So, when one of the oracle requested revert, function getData() in UpdateWeightRunner contract will revert, lead to DoS in multiple functions that need to get data from oracle. It is a big problem for smoothing of protocol, Which mentioned in issue M-4 of the report link: https://github.com/Cyfrin/2024-12-quantamm/blob/main/pkg/pool-quantamm/audit/Cyfrin_1_2_20241217.pdf
Recommendations
Move these checking conditions to _getData() function in UpdateWeightRunner contract when calling oracle, or using try - catch when call _getOracleData() function