DeFiFoundry
60,000 USDC
View results
Submission Details
Severity: high
Invalid

USDC/USDT Blacklist-Induced Liquidation Failure in Zaros Protocol

Summary

The liquidation process within the Zaros protocol can be disrupted if an account marked for liquidation is blacklisted and holds collateral in USDC. Since USDC transfers involving blacklisted addresses are blocked, this prevents the transfer of collateral during liquidation, causing the process to fail. This issue can result in accounts that should be liquidated remaining active, potentially incurring additional losses and creating bad debt for the protocol.

Vulnerability Details

The core issue resides in the withdrawMarginUsd function, a helper function used in the liquidation process, where the protocol attempts to transfer collateral directly to the recipient. If the collateral is USDC or USDT and the account being liquidated is blacklisted, the transfer will fail, causing the entire liquidation process to fail.

Proof of Concept Illustrating the Problem

  1. Liquidation Process Initialization
    The LiquidationBranch.liquidateAccounts() function initiates the liquidation process for accounts identified as liquidatable. One of the steps in this function is the call to TradingAccount.deductAccountMargin() to handle the margin deduction from the account's collateral.

function liquidateAccounts(uint128[] calldata accountsIds) external {
...
for (uint256 i; i < accountsIds.length; i++) {
...
// Deduct maintenance margin from the account's collateral
ctx.liquidatedCollateralUsdX18 = tradingAccount.deductAccountMargin({
feeRecipients: FeeRecipients.Data({
marginCollateralRecipient: globalConfiguration.marginCollateralRecipient,
orderFeeRecipient: address(0),
settlementFeeRecipient: globalConfiguration.liquidationFeeRecipient
}),
pnlUsdX18: requiredMaintenanceMarginUsdX18,
orderFeeUsdX18: UD60x18_ZERO,
settlementFeeUsdX18: ctx.liquidationFeeUsdX18
});
...
}
}
  1. Margin Deduction
    The TradingAccount.deductAccountMargin() function processes the deduction of margin from an account's collateral. It attempts to withdraw the necessary margin in USD terms, including the settlement fee, order fee, and PnL. Here is an example of an attempt to withdraw the settlement fee.

function deductAccountMargin(
Data storage self,
FeeRecipients.Data memory feeRecipients,
UD60x18 pnlUsdX18,
UD60x18 settlementFeeUsdX18,
UD60x18 orderFeeUsdX18
)
internal
returns (UD60x18 marginDeductedUsdX18)
{
...
for (uint256 i; i < cachedCollateralLiquidationPriorityLength; i++) {
...
if (settlementFeeUsdX18.gt(UD60x18_ZERO) && ctx.settlementFeeDeductedUsdX18.lt(settlementFeeUsdX18)) {
(ctx.withdrawnMarginUsdX18, ctx.isMissingMargin) = withdrawMarginUsd(
self,
collateralType,
ctx.marginCollateralPriceUsdX18,
settlementFeeUsdX18.sub(ctx.settlementFeeDeductedUsdX18),
feeRecipients.settlementFeeRecipient
);
ctx.settlementFeeDeductedUsdX18 = ctx.settlementFeeDeductedUsdX18.add(ctx.withdrawnMarginUsdX18);
}
...
}
...
}
  1. Withdrawal and Transfer of Margin
    The withdrawMarginUsd function is responsible for transferring the withdrawn amount to the designated recipient. This function attempts to transfer the collateral directly, which will fail if the collateral is USDC or USDT and the recipient is blacklisted.

function withdrawMarginUsd(
Data storage self,
address collateralType,
UD60x18 marginCollateralPriceUsdX18,
UD60x18 amountUsdX18,
address recipient
)
internal
returns (UD60x18 withdrawnMarginUsdX18, bool isMissingMargin)
{
...
if (marginCollateralBalanceX18.gte(requiredMarginInCollateralX18)) {
withdraw(self, collateralType, requiredMarginInCollateralX18);
uint256 amountToTransfer = marginCollateralConfiguration.convertUd60x18ToTokenAmount(requiredMarginInCollateralX18);
// @audit-issue potential failure if recipient is blacklisted
>>> IERC20(collateralType).safeTransfer(recipient, amountToTransfer);
withdrawnMarginUsdX18 = amountUsdX18;
isMissingMargin = false;
} else {
...
// @audit-issue potential failure if recipient is blacklisted
>>> IERC20(collateralType).safeTransfer(recipient, amountToTransfer);
withdrawnMarginUsdX18 = marginCollateralPriceUsdX18.mul(marginCollateralBalanceX18);
isMissingMargin = true;
}
}

Vulnerability Analysis

  • Blacklisted Address Issue: USDC and USDT are centralized stablecoins, and admins can blacklist addresses. If an account to be liquidated holds USDC and is blacklisted, the safeTransfer call will fail.

  • Function Flow Interruption: When the transfer fails, the withdrawMarginUsd function will revert, causing the deductAccountMargin function to revert, which in turn causes the liquidateAccounts function to fail. This chain reaction halts the entire liquidation process.

  • Accrual of Bad Debt: As the liquidation process fails, the accounts that should be liquidated remain active, potentially incurring more losses. This can lead to the accounts becoming insolvent and accruing bad debt for the protocol, negatively impacting its financial health and stability.

Additional Considerations

Blacklisting can occur intentionally or unintentionally. Intentionally, a malicious actor might deposit USDC in Zaros, open a position on the edge of the position's health, and then perform actions to get blacklisted. If their position becomes liquidatable, the attempted liquidation will revert, blocking other accounts' liquidations as well. Blacklisting can also happen without the user's intent. If administrators blacklist a particular account, and this account has a position in Zaros involved in the liquidation process, it could cause the entire liquidation process to revert again. For this reason, it is very important when dealing with USDC or USDT and there are expected multiple transfers in the same transaction to different accounts, to follow the pull-based withdrawal approach rather than push-based.

Impact

The failure to liquidate accounts due to blacklisted addresses holding USDC/USDT can lead to several severe consequences:

  • Unliquidated accounts and increased losses: Accounts that should be liquidated will remain active. These accounts may incur further losses, exacerbating their financial instability.

  • Protocol bad debt: Ultimately, this can result in bad debt accruing to the protocol, undermining its financial health and stability.

Tools Used

Manual code review

Recommendations

Instead of directly transferring the withdrawn collateral to the account, adopt a pull-based model, allowing accounts to withdraw their collateral themselves. This approach would bypass the blacklisting issue and ensure that the liquidation process can proceed smoothly.

Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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