QuantAMM

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

Chainlink oracle does not work properly if the update intervals (the heartbeat) of the oracles are different.

Summary

In the contract QuantAMMWeightedPool.sol the variable oracleStalenessThreshold is used to avoid stale prices from oracles. To determine the price oracles, protocol used that use the same variable. If the heartbeat of the oracles are different, the protection from stale prices will not be given or the function UpdateWeightRunner::_getData() will get inaccurate value.

Vulnerability Details

The protocol uses the different prices from the Chainlink oracle and verifies the staleness check but as the same oracleStalenessThreshold is same different assets.

Suppose that

  • TokenA price feed has a heartbeat of 86400s.

  • TokenB price feed has a heartbeat of 27s.

By using same oracleStalenessThreshold cause the incorrect prices will get acquired on _getData()

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;
}
}
}

Impact

Stale oracle prices will be used to calculate pool weight updates, leading to incorrect weight adjustments based on outdated market information.

Tools Used

Manual Review

Recommendations

We recommend to use different heart beat for different asset pairs.

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.