DittoETH

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

Secondary liquidations can revert due to difference in used oracle price

Vulnerability Details

In liquidateSecondary the collateral ratio is calculated using LibOracle.getSavedOrSpotOraclePrice().

function liquidateSecondary(
address asset,
MTypes.BatchMC[] memory batches,
uint88 liquidateAmount,
bool isWallet
) external onlyValidAsset(asset) isNotFrozen(asset) nonReentrant {
STypes.AssetUser storage AssetUser = s.assetUser[asset][msg.sender];
MTypes.MarginCallSecondary memory m;
uint256 minimumCR = LibAsset.minimumCR(asset);
// @audit oracle price obtained using LibOracle.getSavedOrSpotOraclePrice()
uint256 oraclePrice = LibOracle.getSavedOrSpotOraclePrice(asset);

https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/facets/MarginCallSecondaryFacet.sol#L38-L47

While in _secondaryLiquidationHelper, the collateral amount corresponding to the debt is calculated using LibOracle.getPrice().

function _secondaryLiquidationHelper(MTypes.MarginCallSecondary memory m) private {
// @dev when cRatio <= 1 liquidator eats loss, so it's expected that only TAPP would call
m.liquidatorCollateral = m.short.collateral;
// @audit oracle price obtained using LibOracle.getPrice()
if (m.cRatio > 1 ether) {
uint88 ercDebtAtOraclePrice =
m.short.ercDebt.mulU88(LibOracle.getPrice(m.asset)); // eth

https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/facets/MarginCallSecondaryFacet.sol#L162-L168

Since LibOracle.getSavedOrSpotOraclePrice() doesn't save the price, a possible mismatch in these prices can result in the transaction reverting when attempting to subtract ercDebtAtOraclePrice from m.short.collateral

function _secondaryLiquidationHelper(MTypes.MarginCallSecondary memory m) private {
// more code
if (m.cRatio > 1 ether) {
@audit wrong assumption of m.short.collateral > ercDebtAtOraclePrice due oracle price difference
s.vaultUser[m.vault][remainingCollateralAddress].ethEscrowed +=
m.short.collateral - ercDebtAtOraclePrice;
// more code

https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/facets/MarginCallSecondaryFacet.sol#L162-L177

Example scenario:

  1. A user calls liquidateSecondary on a postion with debt = 1 and collateral = 2001

  2. The currently saved oracle price is 2020 but was fetched more than 15mins ago. Hence the oracle price used to calculate the collateralRatio is fetched from Chainlink and returns 2000. Hence the collateral ratio calculated is above 1 (2001/2000 > 1).

  3. Inside the _secondaryLiquidationHelper, since the collateral ratio is more than 1, the remaining collateral is calculated using the following equation
    m.short.collateral - ercDebtAtOraclePrice. The ercDebtAtOraclePrice is calculated using the previously saved oracle price which is 2020 which will result in ercDebtAtOraclePrice being 2020 * 1 == 2020.

  4. The transaction will revert when performing 2000 - 2020

Impact

Some secondary liquidations can get reverted for a brief period of time until the oracle price matches up.

Recommendations

Use the price obtained using LibOracle.getSavedOrSpotOraclePrice() in both places.

Updates

Lead Judging Commences

0xnevi Lead Judge
almost 2 years ago
0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Invalidated
Reason: Other
hash 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-563

Support

FAQs

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