QuantAMM

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

Stale Oracle Data Leads to Uncertainty in upliftFee Calculation During removeLiquidity

Summary

When the protocol lacks a backup oracle, it bypasses the oracle staleness check. During liquidity removal, the upliftFee is charged based on price increases using the latest oracle price. If the oracle data is stale, it becomes unclear whether the upliftFee should be applied or not.

Vulnerability Details

During removeLiquidity, the hookAdjustedAmountsOutRaw is calculated in the hook contract based on price changes. In the onAfterRemoveLiquidity function, either a withdrawalFee or an upliftFee is subtracted from hookAdjustedAmountsOutRaw.

/contracts/hooks-quantamm/UpliftOnlyExample.sol:441
441: function onAfterRemoveLiquidity(
442: address router,
443: address pool,
444: RemoveLiquidityKind,
445: uint256 bptAmountIn,
446: uint256[] memory,
447: uint256[] memory amountsOutRaw,
448: uint256[] memory,
449: bytes memory userData
450: ) public override onlySelfRouter(router) returns (bool, uint256[] memory hookAdjustedAmountsOutRaw) {
451: address userAddress = address(bytes20(userData));
452:
453: AfterRemoveLiquidityData memory localData = AfterRemoveLiquidityData({
454: pool: pool,
455: bptAmountIn: bptAmountIn,
456: amountsOutRaw: amountsOutRaw,
457: minAmountsOut: new uint256[](amountsOutRaw.length),
458: accruedFees: new uint256[](amountsOutRaw.length),
459: accruedQuantAMMFees: new uint256[](amountsOutRaw.length),
460: currentFee: minWithdrawalFeeBps,
461: feeAmount: 0,
462: @> prices: IUpdateWeightRunner(_updateWeightRunner).getData(pool), // @audit oracle Staleness
463: lpTokenDepositValueNow: 0,

When there is no backup oracle and the primary oracle is stale, the staleness check in the getData function can be bypassed, potentially leading to incorrect data usage.

/contracts/UpdateWeightRunner.sol:361
361: OracleData memory oracleResult;
362: oracleResult = _getOracleData(OracleWrapper(optimisedOracles[i]));
363: if (oracleResult.timestamp > block.timestamp - oracleStalenessThreshold) {
364: outputData[i] = oracleResult.data;
365: } else {
366: unchecked {
367: numAssetOracles = poolBackupOracles[_pool][i].length;
368: }
369: // @audit if there is no backup oracle ??
370: for (uint j = 1 /*0 already done via optimised poolOracles*/; j < numAssetOracles; ) {
371: oracleResult = _getOracleData(
372: // poolBackupOracles[_pool][asset][oracle]
373: OracleWrapper(poolBackupOracles[_pool][i][j])
374: );
375: if (oracleResult.timestamp > block.timestamp - oracleStalenessThreshold) {
376: // Oracle has fresh values
377: break;
378: } else if (j == numAssetOracles - 1) {
379: // All oracle results for this data point are stale. Should rarely happen in practice with proper backup oracles.
380: revert("No fresh oracle values available");
381: }
382: unchecked {
383: ++j;
384: }
385: }
386: outputData[i] = oracleResult.data;
387: }

From the above code, if optimisedOracles returns a stale price, the control moves to the else block. If no backup oracle exists, the stale optimisedOracles response is returned at line 386. This leads to the following consequences:

  • Missed Fees: If the market price has increased while the stale oracle shows lower price, the protocol fails to collect the correct upliftFee, resulting in a potential loss.

  • Overcharging LPs: If the market price has decreased but the stale oracle reports a higher price, liquidity providers may be overcharged, causing a loss for them.

Impact

Failure to properly detect and handle stale oracle prices can result in:

  • Protocol Losses: Missing the opportunity to charge the correct upliftFee when market prices have increased.

  • Liquidity Provider Losses: Overcharging LPs with an excessive upliftFee based on stale and incorrect price data.

Tools Used

Manual Review

Recommendations

To address the risks of stale oracle data, consider the following approaches:

  1. Disable Withdrawals: Temporarily halt the withdrawal functionality if the oracle is found to be stale, ensuring no transactions occur based on outdated prices.

  2. Allow Fee-Free Withdrawals: If the pool relies on a single oracle that is stale, and the protocol opts to continue allowing withdrawals, suspend fee charges until reliable price updates are restored.

Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

invalid_stale_price_when_no_backup_oracles_set

Cyfrin audit: 7.2.4 Stale Oracle prices accepted when no backup oracles available

Support

FAQs

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

Give us feedback!