Summary
The liquidation action won't be available when the current time is in the last hour of the resetLiquidationTime()
time frame. Causing that the time frame for liquidation will be less than expected by the resetLiquidationTime()
.
Vulnerability Details
The MarginCallPrimaryFacet::_canLiquidate() function helps to determinate if the short
is able to be liquidated or not. If no one has liquidated during 16 hours (resetLiquidationTime()) the flag will be reset and the flagging process begins anew. As the documentation says:
* @dev Shorter has 10 hours after initial flag to bring cRatio up above maintainence margin...
* @dev ...After that, the flagger has 2 hours to liquidate the shorter. If short is not liquidated by shorter within that time, ANYBODY can then liquidate...
* @dev ...After 16 total hours have passed and the short has not been liquidated, the flag gets reset and the flagging process begins anew
The problem is that the short
can't be liquidated exactly at the hour 16
because in the code line 387 the if statement uses a >=
so the code line 395 timeDiff <= resetLiquidationTime
is not possible to be executed:
File: MarginCallPrimaryFacet.sol
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: }
Impact
The liquidaton won't be possible at the last hour specified in the resetLiquidationTime()
time frame. The short
should possible to be liquidated between the time frame from hour 11 to hour 16. Liquidators have less time to be able liquidate shorts
.
Tools used
Manual review
Recommendations
Change the next line so liquidation is possible in the last hour of the resetLiquidationTime()
time frame:
function _canLiquidate(MTypes.MarginCallPrimary memory m)
private
view
returns (bool)
{
...
...
uint256 timeDiff = LibOrders.getOffsetTimeHours() - m.short.updatedAt;
uint256 resetLiquidationTime = LibAsset.resetLiquidationTime(m.asset);
-- if (timeDiff >= resetLiquidationTime) {
++ if (timeDiff > resetLiquidationTime) {
return false;
} else {
uint256 secondLiquidationTime = LibAsset.secondLiquidationTime(m.asset);
bool isBetweenFirstAndSecondLiquidationTime = timeDiff
> LibAsset.firstLiquidationTime(m.asset) && timeDiff <= secondLiquidationTime
&& s.flagMapping[m.short.flaggerId] == msg.sender;
bool isBetweenSecondAndResetLiquidationTime =
timeDiff > secondLiquidationTime && timeDiff <= resetLiquidationTime;
if (
!(
(isBetweenFirstAndSecondLiquidationTime)
|| (isBetweenSecondAndResetLiquidationTime)
)
) {
revert Errors.MarginCallIneligibleWindow();
}
return true;
}
}