DittoETH

Ditto
DeFiFoundryOracle
55,000 USDC
View results
Submission Details
Severity: medium
Valid

Primary short liquidation can not be completed in the last hour of the liquidation timeline

Summary

Shorts flagged for liquidation can not be liquidated in the last and final hour of the liquidation timeline, resulting in the liquidation flag being reset and requiring the short to be flagged again.

Vulnerability Details

If a short's collateral ratio is below the primary liquidation threshold (determined by the LibAsset.primaryLiquidationCR function, by default set to 400%), anyone can flag the position for liquidation by calling the MarginCallPrimaryFacet.flagShort function.

Subsequently, the short position owner has a certain amount of time, specifically, 10 hours (configured and determined by the LibAsset.firstLiquidationTime function), to repay the loan and bring the collateral ratio back above the primary liquidation threshold. If the short position owner fails to do so, the short position can be liquidated by calling the MarginCallPrimaryFacet.liquidate function.

The specific criteria for the liquidation eligibility are defined and determined in the MarginCallPrimaryFacet._canLiquidate function.

contracts/facets/MarginCallPrimaryFacet.sol#L387

351: function _canLiquidate(MTypes.MarginCallPrimary memory m)
352: private
353: view
354: returns (bool)
355: {
... // [...]
383:
384: uint256 timeDiff = LibOrders.getOffsetTimeHours() - m.short.updatedAt;
385: uint256 resetLiquidationTime = LibAsset.resetLiquidationTime(m.asset);
386:
387: ❌ if (timeDiff >= resetLiquidationTime) {
388: return false;
389: } else {
390: uint256 secondLiquidationTime = LibAsset.secondLiquidationTime(m.asset);
391: bool isBetweenFirstAndSecondLiquidationTime = timeDiff
392: > LibAsset.firstLiquidationTime(m.asset) && timeDiff <= secondLiquidationTime
393: && s.flagMapping[m.short.flaggerId] == msg.sender;
394: bool isBetweenSecondAndResetLiquidationTime =
395: timeDiff > secondLiquidationTime && timeDiff <= resetLiquidationTime;
396: if (
397: !(
398: (isBetweenFirstAndSecondLiquidationTime)
399: || (isBetweenSecondAndResetLiquidationTime)
400: )
401: ) {
402: revert Errors.MarginCallIneligibleWindow();
403: }
404:
405: return true;
406: }
407: }

This function checks in lines 387-389 if the elapsed time (timeDiff) since the short was updated is equal or greater than the reset liquidation time (resetLiquidationTime), which is by default set to 16 hours. In this case, the short position has not been liquidated in time and has to be flagged again.

However, this condition conflicts with the isBetweenSecondAndResetLiquidationTime criteria in lines 394-395, specifically, the timeDiff <= resetLiquidationTime check. If the timeDiff value is equal to resetLiquidationTime, both conditions, in line 387 as well as the check in line 395, are true. Due to line 387 taking precedence, the liquidation is considered outdated and the short position has to be flagged again.

Based on the check in lines 67-69 of the flagShort function, it is evident that a short position flagged for liquidation requires re-flagging only if the timeDiff value is greater (>) than the reset liquidation time (resetLiquidationTime):

contracts/facets/MarginCallPrimaryFacet.sol#L67-L69

67: if (timeDiff <= resetLiquidationTime) {
68: revert Errors.MarginCallAlreadyFlagged();
69: }

Thus, the check in line 387 is incorrect, leading to prematurely resetting the short's liquidation flagging status.

As the timestamps are in hours, and the liquidation timeline is relatively short, having an off-by-one error in the liquidation timeline can lead to a significant impact on the liquidations. Concretely, attempting to liquidate a short position in the last hour of the timeline, i.e., timeDiff = 16, is not possible.

Impact

Tools Used

Manual Review

Recommendations

Consider using > instead of >= in line 387 to prevent the liquidation timeline from overlapping with the bounds check in line 395.

Updates

Lead Judging Commences

0xnevi Lead Judge
almost 2 years ago
0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-569

t0x1c Auditor
almost 2 years ago
bernd Submitter
almost 2 years ago
0xnevi Lead Judge
almost 2 years ago
0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-569

Support

FAQs

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