DittoETH

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

Resetting the liquidation flag by partially exiting the flagged short less than expected

Summary

The exitShortWallet(), exitShortErcEscrowed(), and exitShort() of the ExitShortFacet contract are vulnerable to front-running attacks, as the functions calculate the Short's collateral ratio using a cached price, which can be front-run by attackers.

As a result, an attacker can partially exit their flagged Short position by spending less minted asset token (cUSD) than the actual (to buy back the Short's debt) to reset the liquidation flag from their Short position.

Vulnerability Details

Note: the vulnerability affects the exitShortWallet(), exitShortErcEscrowed(), and exitShort() of the ExitShortFacet contract, but only the exitShortWallet() will be explained in this report, as the explanation for the rest functions is the same.

A shorter can execute the exitShortWallet() to partially or fully exit their Short position. To exit a Short, a shorter must give the function some minted asset token (e.g., cUSD) to buy back the Short's debt (ercDebt). If the partial exit is executed on the Short, the exitShortWallet() will invoke the LibShortRecord::maybeResetFlag() .

The maybeResetFlag() will calculate the Short's collateral ratio (cRatio) by calling the LibShortRecord::getCollateralRatio(). If the resulting cRatio is above the primaryLiquidationCR threshold, the function will invoke the LibShortRecord::resetFlag() to reset the liquidation flag of the Short position.

However, the exitShortWallet() is vulnerable to front-running attacks since the getCollateralRatio() will calculate the cRatio using a cached price retrieved from the LibOracle::getPrice().

Let's say Chainlink has updated the price to be higher than the protocol's oracle price (cached). An attacker (i.e., shorter) can front-run the protocol's oracle price update and execute the exitShortWallet() to partially exit their flagged Short. Since the protocol's oracle still retains the lower price (lower debt than the actual), the attacker can spend less minted asset token (cUSD) than the actual (to buy back the Short's debt) to reset the liquidation flag from their Short position, deferring their Short position from being liquidated by the MarginCallPrimaryFacet::liquidate() (Primary liquidation method).

Note that to liquidate the attacker's Short again, a flagger must flag that Short again. It will take at least 10 hours (the default of the firstLiquidationTimeparameter) for the flagged Short to be liquidatable.

Consequently, this vulnerability can indirectly increase bad debt to the Ditto protocol. The protocol can become insolvent, and the protocol's minted stable assets (e.g., cUSD) can eventually be de-pegged.

// FILE: https://github.com/Cyfrin/2023-09-ditto/blob/main/contracts/facets/ExitShortFacet.sol
function exitShortWallet(address asset, uint8 id, uint88 buyBackAmount)
external
isNotFrozen(asset)
nonReentrant
onlyValidShortRecord(asset, msg.sender, id)
{
...
// refund the rest of the collateral if ercDebt is fully paid back
if (buyBackAmount == ercDebt) {
...
} else {
short.ercDebt -= buyBackAmount;
@> short.maybeResetFlag(asset);
}
emit Events.ExitShortWallet(asset, msg.sender, id, buyBackAmount);
}
// FILE: https://github.com/Cyfrin/2023-09-ditto/blob/main/contracts/libraries/LibShortRecord.sol
function maybeResetFlag(STypes.ShortRecord storage short, address asset) internal {
if (short.flaggerId != 0) {
if (
@> LibShortRecord.getCollateralRatio(short, asset)
>= LibAsset.primaryLiquidationCR(asset)
) {
@> LibShortRecord.resetFlag(short);
}
}
}
// FILE: https://github.com/Cyfrin/2023-09-ditto/blob/main/contracts/libraries/LibShortRecord.sol
function getCollateralRatio(STypes.ShortRecord memory short, address asset)
internal
view
returns (uint256 cRatio)
{
@> return short.collateral.div(short.ercDebt.mul(LibOracle.getPrice(asset)));
}
  • https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/facets/ExitShortFacet.sol#L75

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

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

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

Impact

Note: the vulnerability affects the exitShortWallet(), exitShortErcEscrowed(), and exitShort() of the ExitShortFacet contract, but only the exitShortWallet() will be explained in this report, as the explanation for the rest functions is the same.

This vulnerability enables an attacker (i.e., shorter) to partially exit their flagged Short position by spending less minted asset token (cUSD) than the actual (to buy back the Short's debt) to reset the liquidation flag from their Short position, deferring their Short position from being liquidated (Primary liquidation method).

Note that to liquidate the attacker's Short again, a flagger must flag that Short again. It will take at least 10 hours (the default of the firstLiquidationTimeparameter) for the flagged Short to be liquidatable.

Consequently, this vulnerability can indirectly increase bad debt to the Ditto protocol. The protocol can become insolvent, and the protocol's minted stable assets (e.g., cUSD) can eventually be de-pegged.

Tools Used

Manual Review

Recommendations

Since the cached oracle price is prone to front-running attacks, always execute the LibOracle::getOraclePrice() to get the accurate price from Chainlink.

Updates

Lead Judging Commences

0xnevi Lead Judge
almost 2 years ago
0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Invalidated
Reason: Other
serialcoder Submitter
almost 2 years ago
0xnevi Lead Judge
almost 2 years ago
0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Invalidated
Reason: Other

Support

FAQs

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