DittoETH

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

Primary liquidation fee distribution may revert due to the inability to cover the caller fees

Summary

Fee distribution during the primary short liquidation may revert due to an arithmetic underflow error in case the TAPP's escrowed ETH balance is insufficient to cover the caller (liquidator) fees.

Vulnerability Details

During the primary liquidation, the _marginFeeHandler function called in line 126 handles the fee distribution for the liquidator (i.e., caller).

If the eligible caller fee (callerFee) is less or equal to the ETH escrowed by the TAPP, the fee is deducted from TAPP.ethEscrowed and added to the liquidators escrowed ETH balance, VaultUser.ethEscrowed, in lines 271-274.

Otherwise, if the TAPP's escrowed ETH is insufficient to cover the caller fees, i.e., the else branch in line 274, the caller is given the tappFee instead of gasFee.

However, if m.totalFee exceeds the TAPP's ethEscrowed, it reverts with an arithmetic underflow error in line 278. This can be the case if the TAPP has little to no ETH escrowed after placing the forced bid as part of the liquidation, attempting to buy the debt token amount required to repay the short position's debt. In case the short's collateral is not sufficient to buy the debt tokens, the TAPP's escrowed ETH is utilized as well, potentially depleting the TAPP's escrowed ETH.

Consequently, the remaining TAPP.ethEscrowed is potentially lower than the calculated m.totalFee, resulting in the arithmetic underflow error in line 278.

contracts/facets/MarginCallPrimaryFacet.sol#L278

262: function _marginFeeHandler(MTypes.MarginCallPrimary memory m) private {
263: STypes.VaultUser storage VaultUser = s.vaultUser[m.vault][msg.sender];
264: STypes.VaultUser storage TAPP = s.vaultUser[m.vault][address(this)];
265: // distribute fees to TAPP and caller
266: uint88 tappFee = m.ethFilled.mulU88(m.tappFeePct);
267: uint88 callerFee = m.ethFilled.mulU88(m.callerFeePct) + m.gasFee;
268:
269: m.totalFee += tappFee + callerFee;
270: //@dev TAPP already received the gasFee for being the forcedBid caller. tappFee nets out.
271: if (TAPP.ethEscrowed >= callerFee) {
272: TAPP.ethEscrowed -= callerFee;
273: VaultUser.ethEscrowed += callerFee;
274: } else {
275: // Give caller (portion of?) tappFee instead of gasFee
276: VaultUser.ethEscrowed += callerFee - m.gasFee + tappFee;
277: m.totalFee -= m.gasFee;
278: ❌ TAPP.ethEscrowed -= m.totalFee;
279: }
280: }

Impact

The primary short liquidation fails, requiring to wait until the short position's collateral is sufficient to buy the debt tokens or the TAPP has sufficient collateral, or, if the short's collateral ratio further decreases, the short position is liquidated via the secondary liquidation (which adds additional risk to the peg of the asset as the overall collateral ratio could fall below 100%).

Tools Used

Manual Review

Recommendations

Consider checking if the TAPP's ethEscrowed is sufficient to cover the m.totalFee before deducting the fee from the TAPP's ethEscrowed balance and if not, give the caller the TAPP's ethEscrowed balance.

Updates

Lead Judging Commences

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

finding-570

T1MOH Auditor
about 2 years ago
helium Auditor
about 2 years ago
T1MOH Auditor
about 2 years ago
0xnevi Lead Judge
about 2 years ago
0xnevi Lead Judge about 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-570

Support

FAQs

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