DittoETH

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

Discrepancy in Price Fetching Could Lead to Incorrect Flag Resets

Summary

The protocol uses cached Chainlink spot prices to optimize gas usage. The documentation states that any actions related to shorts will attempt to check the latest Oracle price and will occasionally use a cached oracle price to save on gas by saving the last oracleTime and oraclePrice.

Vulnerability Details

The vulnerability comes from the inconsistency in price fetching methods across different functions, particularly those that set or reset flags on shorts. The main function that sets a flag, ‘flag’, uses the ‘getSavedOrSpotOraclePrice’ function to fetch the oracle price, which could either use the saved price or the latest price, depending on whether the latest update has surpassed 15 minutes.

// Flag function
if (
short.getCollateralRatioSpotPrice(LibOracle.getSavedOrSpotOraclePrice(asset))
>= LibAsset.primaryLiquidationCR(asset)
) {
revert Errors.SufficientCollateral();
}
// getSavedOrSpotOraclePrice function
function getSavedOrSpotOraclePrice(address asset) internal view returns (uint256) {
if (LibOrders.getOffsetTime() - getTime(asset) < 15 minutes) {
return getPrice(asset);
} else {
return getOraclePrice(asset);
}
}

However, other functions like ‘increaseCollateral’ and ‘maybeResetFlag’ use the ‘getCollateralRatio’ function, which relies on the ‘getPrice’ function, fetching only the latest saved price in the system.

// maybeResetFlag function
function maybeResetFlag(STypes.ShortRecord storage short, address asset) internal {
if (short.flaggerId != 0) {
if (
LibShortRecord.getCollateralRatio(short, asset)
>= LibAsset.primaryLiquidationCR(asset)
) {
LibShortRecord.resetFlag(short);
}
}
}
// increaseCollateral function
function increaseCollateral(address asset, uint8 id, uint88 amount)
external
isNotFrozen(asset)
nonReentrant
onlyValidShortRecord(asset, msg.sender, id)
{
...
uint256 cRatio = short.getCollateralRatio(asset);
if (cRatio >= Constants.CRATIO_MAX) revert Errors.CollateralHigherThanMax();
...
if (cRatio >= LibAsset.primaryLiquidationCR(asset)) {
short.resetFlag();
}
...
}
// getPrice function
function getPrice(address asset) internal view returns (uint80 oraclePrice) {
AppStorage storage s = appStorage();
return uint80(s.bids[asset][Constants.HEAD].ercAmount);
}

This difference in price fetching methods between functions that influence the same flag can lead to discrepancies, potentially allowing the flag to be incorrectly reset.

Consider the following scenario:

  • Time: 0 hours

    • Oracle Spot Price: $1000 (Saved in the system)

  • Time: 16 minutes

    • A user calls the flag function due to a perceived drop in price.

    • Actual Oracle Spot Price: $800 (The system updates the saved price as it's past 15 minutes from the last update.)

    • The short is correctly flagged, considering the new price drop.

  • Time: 17 minutes

    • The short holder calls increaseCollateral with a minimal amount of ETH.

    • The system uses the outdated higher price from 17 minutes ago to check the collateral ratio in maybeResetFlag.

    • The flag on the short gets incorrectly reset as it perceives the collateral ratio to be healthy with the outdated price.

The short holder calls increaseCollateral with a minimal amount of ETH.
The system uses the outdated higher price from 17 minutes ago to check the collateral ratio in maybeResetFlag.
The flag on the short gets incorrectly reset as it perceives the collateral ratio to be healthy with the outdated price.

Impact

  • The inconsistency in price fetching can be exploited by knowledgeable short holders. They can manipulate the system by intentionally calling functions like ‘increaseCollateral’ or one of the exit short positions that use ‘maybeResetFlag’ to reset the flag.

  • This incorrect reset could cause the original flagger to lose their right to potentially liquidate the short and earn fees, even though the short was correctly flagged due to the latest price.

Tools Used

  • Manual Analysis

Recommendations

  • Standardize the method of fetching prices across all functions that deal with setting or resetting flags. This eliminates the chances of discrepancies in prices between different functions.

  • Specifically, it would be better to modify functions like ‘increaseCollateral’ and ‘maybeResetFlag’ to use the ‘getSavedOrSpotOraclePrice’ function, as this method provides a more up-to-date price compared to using only the latest saved price.

Updates

Lead Judging Commences

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.