DittoETH

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

User's can prevent getting flagged/liquidated by transferring the ShortRecord

Vulnerability Details

ShortRecords with minted NFT's can be transferred even if the collateral ratio is below the liquidation thresholds.

function transferShortRecord(
address asset,
address from,
address to,
uint40 tokenId,
STypes.NFT memory nft
) internal {
AppStorage storage s = appStorage();
STypes.ShortRecord storage short = s.shortRecords[asset][from][nft.shortRecordId];
// @audit doesn't check for collateral raio
if (short.status == SR.Cancelled) revert Errors.OriginalShortRecordCancelled();
if (short.flaggerId != 0) revert Errors.CannotTransferFlaggedShort();
deleteShortRecord(asset, from, nft.shortRecordId);

https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/libraries/LibShortRecord.sol#L120-L132

This allows an user having a ShortRecord with low collateral ratio to transfer it to another of their own address by front running a flag/liquidation. Since the flagger/liquidator has to pass in the (shorter,id) to specify the short, any attempt to flag/liquidate the short will fail.

function flagShort(address asset, address shorter, uint8 id, uint16 flaggerHint)

https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/facets/MarginCallPrimaryFacet.sol#L43C36-L43C36

POC Test

diff --git a/test/MarginCallFlagShort.t.sol b/test/MarginCallFlagShort.t.sol
index 906657e..9032395 100644
--- a/test/MarginCallFlagShort.t.sol
+++ b/test/MarginCallFlagShort.t.sol
@@ -101,6 +101,36 @@ contract MarginCallFlagShortTest is MarginCallHelper {
assertEq(diamond.getFlagger(shortRecord.flaggerId), extra);
}
+ function test_Revert_FlaggerMovesTheToken() external {
+ fundLimitBidOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, receiver);
+ fundLimitShortOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, sender);
+ STypes.ShortRecord memory shortRecord =
+ diamond.getShortRecord(asset, sender, Constants.SHORT_STARTING_ID);
+
+ assertEq(diamond.getFlaggerIdCounter(), 1);
+ assertEq(shortRecord.flaggerId, 0);
+ assertEq(diamond.getFlagger(shortRecord.flaggerId), address(0));
+
+ //make short flaggable
+ skip(2 hours);
+ setETH(2500 ether);
+
+ //owner transfers the short before user flags
+ address senderAlternativeAddress = address(314);
+ vm.startPrank(sender);
+ diamond.mintNFT(asset,Constants.SHORT_STARTING_ID);
+ shortRecord =
+ diamond.getShortRecord(asset, sender, Constants.SHORT_STARTING_ID);
+ diamond.transferFrom(sender,senderAlternativeAddress,shortRecord.tokenId);
+ vm.stopPrank();
+
+ //the tx to flag short fails due to changed ownership of short
+ vm.prank(extra);
+ vm.expectRevert(Errors.InvalidShortId.selector);
+ diamond.flagShort(asset, sender, Constants.SHORT_STARTING_ID, Constants.HEAD);
+
+ }
+
function checkShortRecordAfterReset() public {
STypes.ShortRecord memory shortRecord =
diamond.getShortRecord(asset, sender, Constants.SHORT_STARTING_ID);

Impact

Flaggers/liquidators might end up wasting gas without flagging/liquidating the short record and the system will have low collateral ratio.

Recommendations

Disallow transferring a short record if the collateral ratio is below the primary liquidation threshold.

diff --git a/contracts/libraries/LibShortRecord.sol b/contracts/libraries/LibShortRecord.sol
index 7c5ecc3..9953d34 100644
--- a/contracts/libraries/LibShortRecord.sol
+++ b/contracts/libraries/LibShortRecord.sol
@@ -128,6 +128,10 @@ library LibShortRecord {
STypes.ShortRecord storage short = s.shortRecords[asset][from][nft.shortRecordId];
if (short.status == SR.Cancelled) revert Errors.OriginalShortRecordCancelled();
if (short.flaggerId != 0) revert Errors.CannotTransferFlaggedShort();
+ if (
+ getCollateralRatioSpotPrice(short, LibOracle.getSavedOrSpotOraclePrice(asset))
+ < LibAsset.primaryLiquidationCR(asset)
+ ) revert Errors.InsufficientCollateral();
deleteShortRecord(asset, from, nft.shortRecordId);
Updates

Lead Judging Commences

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

finding-610

Support

FAQs

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