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
.
Shorts 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.
All 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.
The 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.
Further 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)
).
contracts/facets/MarginCallSecondaryFacet.sol#L177
The value of the debt, ercDebtAtOraclePrice
, is calculated based on the currently cached price, as the LibOracle.getPrice
function returns the stored price.
[!NOTE]
The initially retrieved Oracle price at the beginning of the liquidation call, returned by theLibOracle.getSavedOrSpotOraclePrice
function, does not store the retrieved spot price in storage if the cached price is stale.
Consequently, there are potentially two different asset prices used. The asset's spot price and the cached, stale oracle price.
Consider 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.
This is demonstrated in the following example:
Consider the following liquidateable short position (simplified, ignores decimal precision for this demonstration):
Collateral | Debt | Collateralization Ratio (based on spot price) | Price ETH/USD | Spot Price TOKEN/ETH | Cached Price TOKEN/ETH |
---|---|---|---|---|---|
1 ETH | 1400 TOKEN | $$ | 2000 | 0.0005 | 0.00075 |
Calculating the ercDebtAtOraclePrice
with the cached oracle price 0.00075
for TOKEN/ETH, returned by the LibOracle.getPrice
function, results in:
The resulting debt value, quoted in ETH, is 1.05 ETH
, which is larger than the short's available collateral, m.short.collateral = 1 ETH
.
This results in an arithmetic underflow error attempting to subtract the calculated ercDebtAtOraclePrice
from m.short.collateral
in line 177.
Specifically, this scenario occurs in the following situation:
A user opens a short position with a collateral of and a debt of at TOKEN/ETH price of -> Debt in ETH: -> CR =
The spot (oracle) price of TOKEN/ETH increases from to -> Debt in ETH: -> CR = (eligible for secondary liquidation - also for primary liquidation due to < 110%)
New orders for the TOKEN asset are added to the order book, leading to the oracle price being updated/cached to per TOKEN
~15min after the price got updated and cached, the TOKEN/ETH spot price decreases from to . The CR improves -> CR =
Secondary liquidation is attempted to liquidate the short (primary short liquidation is not possible due to the 110% CR limit)
During 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 -> Debt in ETH: -> CR = $ 1 / 0.7 \approx 142\%$
In line 168, ercDebtAtOraclePrice
is calculated based on the previously cached oracle price of ->
In line 176, m.short.collateral
is subtracted by ercDebtAtOraclePrice
-> -> arithmetic underflow error -> reverts!
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.
Manual Review
Consider also using the minimum of the m.short.collateral
and ercDebtAtOraclePrice
values, as similarly done in lines 204-205.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.