Secondary short liquidation reverts due to arithmetic underflow in volatile market conditions
The `ercDebtAtOraclePrice` is calculated based on the cached Oracle price, which is not updated with the retrieved, potentially fresh spot price due to the 15-minute staleness limit at the beginning of the secondary liquidation call. This results in the `ercDebtAtOraclePrice` being greater than the short's available collateral, resulting in an underflow error when attempting to subtract the calculated `ercDebtAtOraclePrice` from the `m.short.collateral`.\\nShorts with a collateral ratio below `secondaryLiquidationCR`, i.e., 150% by default, can be liquidated in batches via the secondary liquidation mechanism, executed via the `MarginCallSecondaryFacet.liquidateSecondary` function.\\nAll shorts within the batch are iterated, and for each short, important values are kept in memory within the `MTypes.MarginCallSecondary` struct, evaluated in the `_setMarginCallStruct` function. The collateral ratio, `m.cRatio`, is calculated via the `LibShortRecord.getCollateralRatioSpotPrice` function, based on the given oracle price.\\nThe Oracle price is determined by the `LibOracle.getSavedOrSpotOraclePrice` function in line 47, which either returns the current spot price if the cached price is stale (older than 15 min) or the cached price.\\n```\\nfunction getSavedOrSpotOraclePrice(address asset) internal view returns (uint256) {\\n    if (LibOrders.getOffsetTime() - getTime(asset) < 15 minutes) {\\n        return getPrice(asset);\\n    } else {\\n        return getOraclePrice(asset);\\n    }\\n}\\n```\\n\\nFurther on, the liquidation proceeds in the `_secondaryLiquidationHelper` function. If the short's `cRatio` is greater than 100% in line 166, the remaining collateral (i.e., the collateral minus the debt) is refunded. It is either refunded to the shorter if the `cRatio` is greater than 110% (m.minimumCR), or, otherwise, to the TAPP (address(this)).\\ncontracts/facets/MarginCallSecondaryFacet.sol#L177\\n```\\nfunction _secondaryLiquidationHelper(MTypes.MarginCallSecondary memory m) private {\\n    // @dev when cRatio <= 1 liquidator eats loss, so it's expected that only TAPP would call\\n    m.liquidatorCollateral = m.short.collateral;\\n    if (m.cRatio > 1 ether) {\\n        uint88 ercDebtAtOraclePrice =\\n            m.short.ercDebt.mulU88(LibOracle.getPrice(m.asset)); // eth\\n        m.liquidatorCollateral = ercDebtAtOraclePrice;\\n        // if cRatio > 110%, shorter gets remaining collateral\\n        // Otherwise they take a penalty, and remaining goes to the pool\\n        address remainingCollateralAddress =\\n            m.cRatio > m.minimumCR ? m.shorter : address(this);\\n        s.vaultUser[m.vault][remainingCollateralAddress].ethEscrowed +=\\n❌          m.short.collateral - ercDebtAtOraclePrice;\\n    }\\n    LibShortRecord.disburseCollateral(\\n        m.asset,\\n        m.shorter,\\n        m.short.collateral,\\n        m.short.zethYieldRate,\\n        m.short.updatedAt\\n    );\\n    LibShortRecord.deleteShortRecord(m.asset, m.shorter, m.short.id);\\n}\\n```\\n\\nThe value of the debt, `ercDebtAtOraclePrice`, is calculated based on the currently cached price, as the `LibOracle.getPrice` function returns the stored price.\\n[!NOTE] The initially retrieved Oracle price at the beginning of the liquidation call, returned by the `LibOracle.getSavedOrSpotOraclePrice` function, does not store the retrieved spot price in storage if the cached price is stale.\\nConsequently, there are potentially two different asset prices used. The asset's spot price and the cached, stale oracle price.\\nConsider the case where there is a significant difference between the spot price and the cached price. This would calculate the `m.cRatio` based on the spot price and the `ercDebtAtOraclePrice` based on the cached price.\\nThis is demonstrated in the following example:\\nConsider the following liquidateable short position (simplified, ignores decimal precision for this demonstration):\\nCollateral Debt Collateralization Ratio (based on spot price) Price ETH/USD Spot Price TOKEN/ETH Cached Price TOKEN/ETH\\n1 ETH 1400 TOKEN $${1 \\over {1400 * 0.0005}} \\approx 142\\%$$ 2000 0.0005 0.00075\\nCalculating the `ercDebtAtOraclePrice` with the cached oracle price `0.00075` for TOKEN/ETH, returned by the `LibOracle.getPrice` function, results in:\\n$$ \\begin{align} ercDebtAtOraclePrice &= debt \\cdot price \\ &= 1400 \\cdot 0.00075 \\ &= 1.05 \\text{ ETH} \\end{align} $$\\nThe resulting debt value, quoted in ETH, is `1.05 ETH`, which is larger than the short's available collateral, `m.short.collateral = 1 ETH`.\\nThis results in an arithmetic underflow error attempting to subtract the calculated `ercDebtAtOraclePrice` from `m.short.collateral` in line 177.\\nSpecifically, this scenario occurs in the following situation:\\nA user opens a short position with a collateral of $1 \\text{ ETH}$ and a debt of $1400 \\text{ TOKEN}$ at TOKEN/ETH price of $0.00014286 \\text{ ETH}$ -> Debt in ETH: $1400 * 0.00014286 = 0.2 \\text{ ETH}$ -> CR = $1/0.2 = 500\\%$\\nThe spot (oracle) price of TOKEN/ETH increases from $0.00014286 \\text{ ETH}$ to $0.00075 \\text{ ETH}$ -> Debt in ETH: $1400 * 0.00075 = 1.05 \\text{ ETH}$ -> CR = $1 / 1.05 \\approx 95\\%$ (eligible for secondary liquidation - also for primary liquidation due to < 110%)\\nNew orders for the TOKEN asset are added to the order book, leading to the oracle price being updated/cached to $0.00075 \\text{ ETH}$ per TOKEN\\n~15min after the price got updated and cached, the TOKEN/ETH spot price decreases from $0.00075 \\text{ ETH}$ to $0.0005 \\text{ ETH}$. The CR improves -> CR = $1/(1400 * 0.0005) \\approx 142\\%$\\nSecondary liquidation is attempted to liquidate the short (primary short liquidation is not possible due to the 110% CR limit)\\nDuring the secondary liquidation call, `m.cRatio` is calculated based on the recent spot price (in step 4, due to cached price older than 15min) of $0.0005 \\text{ ETH}$ -> Debt in ETH: $1400 * 0.0005 = 0.7 \\text{ ETH}$ -> CR = $ 1 / 0.7 \\approx 142\\%$\\nIn line 168, `ercDebtAtOraclePrice` is calculated based on the previously cached oracle price of $0.00075 \\text{ ETH}$ -> $1400 * 0.00075 = 1.05 \\text{ ETH}$\\nIn line 176, `m.short.collateral` is subtracted by `ercDebtAtOraclePrice` -> $1 - 1.05= -0.05 \\text{ ETH}$ -> arithmetic underflow error -> reverts!
Consider also using the minimum of the `m.short.collateral` and `ercDebtAtOraclePrice` values, as similarly done in lines 204-205.
The secondary short liquidation mechanism reverts in certain market situations, forcing liquidators to wait for the CR to decrease further to be able to use the primary liquidation mechanism. This puts the overall collateral ratio and, thus the asset peg under pressure as liquidations can not be executed in a timely manner.
```\\nfunction getSavedOrSpotOraclePrice(address asset) internal view returns (uint256) {\\n    if (LibOrders.getOffsetTime() - getTime(asset) < 15 minutes) {\\n        return getPrice(asset);\\n    } else {\\n        return getOraclePrice(asset);\\n    }\\n}\\n```\\n