DittoETH

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

Resetting the liquidation flag using less collateral than expected

Summary

The ShortRecordFacet::increaseCollateral() is vulnerable to front-running attacks, as the function calculates the Short's collateral ratio using a cached price, which can be front-run by attackers.

As a result, an attacker can spend less collateral than the actual to reset the liquidation flag from their flagged Short position.

Vulnerability Details

The increaseCollateral() can be invoked by a shorter to increase collateral (zETH) of their active Short position. After increasing the collateral, the increaseCollateral() will execute the LibShortRecord::getCollateralRatio() to calculate the Short's collateral ratio (cRatio). Then, 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 increaseCollateral() 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 increaseCollateral(). Since the protocol's oracle still retains the lower price (lower debt than the actual), the attacker can spend less collateral than the actual to reset the liquidation flag, 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/ShortRecordFacet.sol
function increaseCollateral(address asset, uint8 id, uint88 amount)
external
isNotFrozen(asset)
nonReentrant
onlyValidShortRecord(asset, msg.sender, id)
{
STypes.Asset storage Asset = s.asset[asset];
uint256 vault = Asset.vault;
STypes.Vault storage Vault = s.vault[vault];
STypes.VaultUser storage VaultUser = s.vaultUser[vault][msg.sender];
if (VaultUser.ethEscrowed < amount) revert Errors.InsufficientETHEscrowed();
STypes.ShortRecord storage short = s.shortRecords[asset][msg.sender][id];
short.updateErcDebt(asset);
uint256 yield = short.collateral.mul(short.zethYieldRate);
short.collateral += amount;
@> uint256 cRatio = short.getCollateralRatio(asset);
if (cRatio >= Constants.CRATIO_MAX) revert Errors.CollateralHigherThanMax();
//@dev reset flag info if new cratio is above primaryLiquidationCR
@> if (cRatio >= LibAsset.primaryLiquidationCR(asset)) {
@> short.resetFlag();
@> }
yield += amount.mul(Vault.zethYieldRate);
short.zethYieldRate = yield.divU80(short.collateral);
VaultUser.ethEscrowed -= amount;
Vault.zethCollateral += amount;
Asset.zethCollateral += amount;
emit Events.IncreaseCollateral(asset, msg.sender, id, amount);
}
// 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/ShortRecordFacet.sol#L55

  • https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/facets/ShortRecordFacet.sol#L59-L61

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

Impact

This vulnerability enables an attacker (i.e., shorter) to spend less collateral than the actual to reset the liquidation flag from their flagged 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.

The 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
Validated
Assigned finding tags:

finding-567

Support

FAQs

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