DittoETH

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

When combining shorts, there is no check that the combined short is not above the max collateral ratio

Summary

The documentation states that the max collateral ratio on a short is 15x. When you open a short or increase collateral, there is a check to be sure that your position isn't over the max collateral ratio, but there is no check for this when you combine shorts. If the purpose is to enforce that people are capital efficient, then it makes sense to prevent the combined short from exceeding the max collateral ratio when shorts are combined as well.

Vulnerability Details

function combineShorts(address asset, uint8[] memory ids)
external
isNotFrozen(asset)
nonReentrant
onlyValidShortRecord(asset, msg.sender, ids[0])
{
if (ids.length < 2) revert Errors.InsufficientNumberOfShorts();
// First short in the array
STypes.ShortRecord storage firstShort = s.shortRecords[asset][msg.sender][ids[0]];
// @dev Load initial short elements in struct to avoid stack too deep
MTypes.CombineShorts memory c;
c.shortFlagExists = firstShort.flaggerId != 0;
c.shortUpdatedAt = firstShort.updatedAt;
address _asset = asset;
uint88 collateral;
uint88 ercDebt;
uint256 yield;
uint256 ercDebtSocialized;
for (uint256 i = ids.length - 1; i > 0; i--) {
uint8 _id = ids[i];
_onlyValidShortRecord(_asset, msg.sender, _id);
STypes.ShortRecord storage currentShort =
s.shortRecords[_asset][msg.sender][_id];
// See if there is at least one flagged short
if (!c.shortFlagExists) {
if (currentShort.flaggerId != 0) {
c.shortFlagExists = true;
}
}
//@dev Take latest time when combining shorts (prevent flash loan)
if (currentShort.updatedAt > c.shortUpdatedAt) {
c.shortUpdatedAt = currentShort.updatedAt;
}
{
uint88 currentShortCollateral = currentShort.collateral;
uint88 currentShortErcDebt = currentShort.ercDebt;
collateral += currentShortCollateral;
ercDebt += currentShortErcDebt;
yield += currentShortCollateral.mul(currentShort.zethYieldRate);
ercDebtSocialized += currentShortErcDebt.mul(currentShort.ercDebtRate);
}
if (currentShort.tokenId != 0) {
//@dev First short needs to have NFT so there isn't a need to burn and re-mint
if (firstShort.tokenId == 0) {
revert Errors.FirstShortMustBeNFT();
}
LibShortRecord.burnNFT(currentShort.tokenId);
}
// Cancel this short and combine with short in ids[0]
LibShortRecord.deleteShortRecord(_asset, msg.sender, _id);
}
// Merge all short records into the short at position id[0]
firstShort.merge(ercDebt, ercDebtSocialized, collateral, yield, c.shortUpdatedAt);
// If at least one short was flagged, ensure resulting c-ratio > primaryLiquidationCR
if (c.shortFlagExists) {
if (
firstShort.getCollateralRatioSpotPrice(
LibOracle.getSavedOrSpotOraclePrice(_asset)
) < LibAsset.primaryLiquidationCR(_asset)
) revert Errors.InsufficientCollateral();
// Resulting combined short has sufficient c-ratio to remove flag
firstShort.resetFlag();
}
emit Events.CombineShorts(asset, msg.sender, ids);

Impact

You are allowing people to be less capital efficient than you want. If you are going to have a max collateral ratio to encourage capital efficency, it should also apply to combined shorts.

Tools Used

Manual review

Recommendations

Add the following code at the end of combineShort function:

uint256 cRatio = firstShort.getCollateralRatio(asset);
if (cRatio >= Constants.CRATIO_MAX) revert Errors.CollateralHigherThanMax();
Updates

Lead Judging Commences

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

finding-411

Support

FAQs

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