DittoETH

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

The liquidation process is open for one hour less than expected

Summary

The liquidation process is open for one hour less than expected.

Vulnerability Details

The primary margin call is executed calling MarginCallPrimaryFacet.sol:liquidate. This function calls _canLiquidate to check if the short is eligible for liquidation.

Given the following configuration parameters:

  • firstLiquidationTime = 10 hours;

  • secondLiquidationTime = 12 hours;

  • resetLiquidationTime = 16 hours;

The expected behavior is that, starting from the time the short is flagged:

  • Shorter has 10 hours to bring cRatio up above the maintenance margin.

  • Passed 10 hours the flagger can liquidate the shorter.

  • Passed 2 more hours (12 hours total) anyone can liquidate the shorter.

  • Passed 4 more hours (16 hours total) the flag gets reset.

However, that is not the case, as the liquidation is open for anyone for 3 hours instead of 4 hours. This is because although in line 395 it is checked that timeDiff <= resetLiquidationTime, it can never reach that point with timeDiff == resetLiquidationTime because of the >= operator in line 387, that should be > instead.

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 }

Also, if we look at the code of the flagShort function we find the following:

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

So, in the event of timeDiff being equal to resetLiquidationTime, neither a new flagger can be set nor the short can be liquidated.

Proof of Concept

An easier way of spotting this bug is with the following configuration parameters:

  • firstLiquidationTime = 1 hours;

  • secondLiquidationTime = 2 hours;

  • resetLiquidationTime = 3 hours;

Let's see what would happen depending on the time elapsed since the short was flagged:

  • 1 hour: None of the conditions are met, so the function reverts.

  • 2 hours: If called by the flagger isBetweenFirstAndSecondLiquidationTime is true and can liquidate the short. If called by anyone else, the function reverts.

  • 3 hours: timeDiff >= resetLiquidationTime is true, so the flagger is reset.

As we can see, there is no interval of time where anyone can liquidate the short and the short is only liquidatable by the flagger for 1 hour.

Impact

Users will have 1 hour less than expected to liquidate the short, which could obstruct the liquidation process.

Tools Used

Manual review.s

Recommendations

File: contracts/facets/MarginCallPrimaryFacet.sol
- if (timeDiff >= resetLiquidationTime) {
+ if (timeDiff > resetLiquidationTime) {
Updates

Lead Judging Commences

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.