DittoETH

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

Flagger Ids are reused too early, potentially blocking flaggers from liquidating in there allocated time

Summary

The protocol enables users to flag positions that fall below the primary collateral ratio. Subsequently, the shorter is granted a time frame to restore their position above this ratio to avoid liquidation. If the position remains below the primary collateral ratio, the flagger attains the exclusive right to liquidate it before anyone else.

Vulnerability Details

To optimize the process, the protocol reuses flagger IDs. However, a flaw exists in the protocol where a flagger ID is available for reuse after the firstLiquidationTime instead of after the secondLiquidationTime.

//@dev re-use an inactive flaggerId
if (timeDiff > LibAsset.firstLiquidationTime(cusd)) {
delete s.assetUser[cusd][flaggerToReplace].g_flaggerId;
short.flaggerId = flagStorage.g_flaggerId = flaggerHint;

This premature reuse of the flagger ID can block a flagger from liquidating a position during their allocated slot, which spans between firstLiquidationTime and secondLiquidationTime.

uint256 secondLiquidationTime = LibAsset.secondLiquidationTime(m.asset);
bool isBetweenFirstAndSecondLiquidationTime = timeDiff
> LibAsset.firstLiquidationTime(m.asset) && timeDiff <= secondLiquidationTime
&& s.flagMapping[m.short.flaggerId] == msg.sender;
Click to expand Proof of Concept
function testShortFlagReusedTooEarly() public {
skipTimeAndSetEth(2 hours, 4000 ether);
// Create short 1
fundLimitShortOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, sender);
// Create short 2
fundLimitShortOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, sender);
// match short 1
fundLimitBidOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, receiver);
// match short 2
fundLimitBidOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, receiver);
// extra Ask for liquidation
fundLimitAskOpt(DEFAULT_PRICE, DEFAULT_AMOUNT , extra);
// skip time, price fall
skipTimeAndSetEth(2 hours, 2000 ether);
// Extra user flag short 1
vm.prank(extra);
diamond.flagShort(asset, sender, Constants.SHORT_STARTING_ID, Constants.HEAD);
// skip user grace period
skipTimeAndSetEth(11 hours, 2000 ether);
// receiver flags short 2
vm.prank(receiver);
diamond.flagShort(asset, sender, Constants.SHORT_STARTING_ID + 1, Constants.HEAD);
vm.startPrank(extra);
// extra user tries to liquidate short 1 in the valid time range but flag is reused so fails
vm.expectRevert(Errors.MarginCallIneligibleWindow.selector);
diamond.liquidate(
asset, sender, Constants.SHORT_STARTING_ID, shortHintArrayStorage
);
vm.stopPrank();
}

Impact

Flaggers is unable to liquidate short positions during their designated time slots

Tools Used

  • Manual Analysis

  • Foundry

Recommendations

Ensure that flagger IDs are reused only after the secondLiquidationTime.

if (timeDiff > LibAsset.secondLiquidationTime(cusd)) {
delete s.assetUser[cusd][flaggerToReplace].g_flaggerId;
short.flaggerId = flagStorage.g_flaggerId = flaggerHint;
Updates

Lead Judging Commences

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

finding-533

Support

FAQs

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