The liquidation flagger can be griefed from liquidating a short within the ~10hrs - ~12hrs
liquidation timeline by front-running the liquidation call and re-using the incorrectly expired flagger id by flagging another short.
To liquidate a short position as part of the primary liquidation mechanism, the short must be first flagged via the MarginCallPrimaryFacet.flagShort
function. After flagging, the short position owner has some time (by default 10 hours
) to bring the collateral ratio up above the primary liquidation margin (LibAsset.primaryLiquidationCR(..)
) to prevent the liquidation.
If the collateral ratio is not brought up above the primary liquidation margin within the given time frame, the primary liquidation is executable.
Based on the outlined liquidation timeline, the liquidation flagger is exclusively able to liquidate the short within two hours. This is determined in lines 391-393 of the _canLiquidate
function:
The flagMapping
mapping is checked to ensure only the liquidation flagger can call this function. Otherwise, the function will revert with the Errors.MarginCallIneligibleWindow
error if called by anyone other than the flagger during the hours ~10hrs - ~12hrs
.
Let's examine the LibShortRecord.setFlagger
function, called within the MarginCallPrimaryFacet.flagShort
function:
The flagger of a short is identified by the flaggerId
, which is associated with the caller address (msg.sender
) in the flagMapping
storage mapping.
Flagger ids are re-usable once a flag is expired, i.e., the time delta between now and the flag's update timestamp (g_updatedAt
) is greater than the first liquidation time LibAsset.firstLiquidationTime(..)
, which is by default 10 hours
. See line 394 of the setFlagger
function.
Once a flag is expired, another liquidator can re-use this same flaggerId
by calling the setFlagger
function and providing the flaggerHint
equal to the expired flagger ID. This will overwrite the flagMapping
entry for the expired flagger ID with the new caller address (msg.sender
), which is the other liquidator.
However, determining the flagger id expiry by checking if timeDiff > LibAsset.firstLiquidationTime(cusd)
in line 394 collides with the liquidation eligibility check in _canLiquidate
in lines 391-393, which also checks the timeDiff
against LibAsset.firstLiquidationTime(..)
.
This effectively means that once the liquidation flagger is able to execute the liquidation via the MarginCallPrimaryFacet.liquidate
function, due to the isBetweenFirstAndSecondLiquidationTime
condition in lines 391-393 evaluating to true
, the flagger id is already considered expired and can be re-used by another liquidator. If the other liquidator front-runs, by re-using the same flagger id, the s.flagMapping[m.short.flaggerId] == msg.sender
check in line 393 is false
, preventing the liquidation.
Liquidation flagger ids are considered expired too soon and can be re-used by other users, preventing the liquidation of the short position within the first liquidation time frame (i.e., ~10hrs - ~12hrs
).
Manual Review
Consider using LibAsset.resetLiquidationTime(..)
(i.e., default ~16 hours
) in the setFlagger
function to determine the expiry of a flagger id instead of the LibAsset.firstLiquidationTime
function to avoid the griefing attack.
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.