Back to blogs
Written by
Dacian
Published on
February 11, 2025

DeFi Liquidation Vulnerabilities and Mitigation Strategies

Discover key vulnerabilities in DeFi liquidation code, potential exploits, and best practices to safeguard protocol solvency and user trust.

Table of Contents

Originally published on Dacian's blog as DeFi Liquidation Vulnerabilities on 23 January 2025.

Introduction

Efficient liquidation is vital for decentralized finance (DeFi) solvency but challenging to implement securely, especially in trustless systems. High bug density can threaten protocol stability and user trust. Developers and auditors should assess these key vulnerability classes to mitigate risks.

Let’s start by exploring two frequently used terms with their definitions:

  • Liquidatable: When collateral_value * loan_to_value_ratio < borrow_value, meaning enough collateral exists to cover the loan, preventing bad debt if liquidated in time.
  • Insolvent: When collateral_value < borrow_value, meaning insufficient collateral remains, leading to bad debt even after liquidation.

Liquidation should occur promptly to prevent liquidatable positions from becoming insolvent.

No liquidation incentive

Decentralized protocols often rely on trustless liquidators rather than designated entities. To ensure timely liquidations, they offer incentives like bonuses or rewards. MEV bots, for example, liquidate positions if the reward exceeds gas costs, making it a consistent, low-risk profit opportunity.

Guideline: If the protocol depends on trustless liquidators, does it offer sufficient incentives?

No incentive to liquidate small positions

Without minimum deposit and position size requirements, small debt positions may accumulate, remaining unliquidated due to a lack of financial incentive. This is particularly risky for stablecoin protocols, as bad debt can pile up, leading to under-collateralization.

Mitigation:

Guideline: Does the protocol enforce a minimum position size across all functions that modify positions, or only when creating a new one?

Additional cases: [1, 2, 3, 4, 5]

Profitable user withdraws all collateral, eliminating liquidation incentive

In trading protocols like perpetuals, a user’s open long/short position includes their current profit/loss (PNL) when calculating total collateral value. If users have a high positive PNL, they may withdraw most or all of their deposited collateral while still appearing solvent.

If their PNL later declines, the position becomes liquidatable. However, with no collateral left to seize, liquidators lack incentives, potentially leading to liquidation failure or a transaction revert, ultimately resulting in insolvency.

Mitigation

  • Require users with open positions to maintain a minimum collateral balance, regardless of their PNL. 
  • Discount positive PNL, reducing its collateral weight compared to actually deposited assets.

Additionally, allowing users to borrow deposited collateral without restrictions can create similar risks and should be carefully managed.

Guideline: Can profitable users withdraw their deposited collateral? If so, what happens if market conditions turn against them?

Additional cases: [1]

No mechanism to manage bad debt

If a liquidatable position isn’t addressed quickly, it can become insolvent, where the liquidation reward and seized collateral are worth less than the debt tokens needed to cover the bad debt.

In this case, trustless liquidators have no incentive to liquidate, allowing bad debt to accumulate. Some protocols may not handle this state properly, leading to liquidation transactions failing or reverting, making insolvent positions impossible to liquidate.

Mitigation:

  • Use trusted liquidators to ensure all liquidatable positions are processed promptly.
  • Establish an insurance fund, funded by protocol fees, to absorb bad debt and keep liquidations profitable for trustless liquidators.
  • Distribute bad debt across protocol participants, such as liquidity providers.

Guideline: Does the protocol have a mechanism to handle bad debt? What happens when an insolvent position is liquidated?

Additional cases: [1, 2]

Partial liquidation bypasses bad debt accounting

Consider this liquidation code:

// additional processing when position closed by liquidation
if (!hasPosition) {
    int256 remainingMargin = vault.margin;


    // credit positive margin to vault recipient
    if (remainingMargin > 0) {
        if (vault.recipient != address(0)) {
            vault.margin = 0;


            sentMarginAmount = uint256(remainingMargin);


            ERC20(pairStatus.quotePool.token).safeTransfer(
                vault.recipient, sentMarginAmount);
        }
    }
    // otherwise ensure liquidator covers bad debt
    else if (remainingMargin < 0) {
        vault.margin = 0;


        // any losses that cannot be covered by the vault
        // must be compensated by the liquidator
        ERC20(pairStatus.quotePool.token).safeTransferFrom(
            msg.sender, address(this), uint256(-remainingMargin));
    }
}

If a position is fully liquidated, the code ensures the liquidator covers any associated bad debt. However, since this check only applies to full liquidations, a liquidator can avoid bad debt responsibility by only partially liquidating the position.

This loophole allows bad debt to accumulate unaccounted for within the protocol. A possible mitigation is requiring that partial liquidations of insolvent positions also account for a proportional share of the bad debt, either through an insurance fund or a bad debt socialization mechanism.

Guideline: Is bad debt accounted for during partial liquidation of an insolvent position?

No partial liquidation prevents whale liquidation

In protocols where trustless liquidators provide debt tokens to cover a borrower’s bad debt, partial liquidation is essential to allow large positions to be liquidated in smaller portions. Without partial liquidation, large liquidatable positions created by whales may remain unresolved since individual liquidators might lack the required tokens to cover the entire debt.

Flash loans can help liquidate large positions, but only if the loan amount does not exceed available market liquidity, which is not always guaranteed.

Guideline: Does the protocol support partial liquidation? If not, how can a whale user be liquidated? Are there other safeguards, such as caps on maximum position size? If so, how effective are these safeguards in ensuring that the largest positions can be liquidated?

Liquidation denial of service

If an attacker can indefinitely block liquidations or make themselves immune to liquidation, it poses a severe risk to protocol solvency by allowing bad debt to accumulate unchecked. Audits of real-world protocols have uncovered several attack methods that exploit this vulnerability.

Attacker uses many small positions to prevent liquidation

Consider this liquidation code, which loops through all of a user’s active positions:

function _removePosition(uint256 positionId) internal {
    address trader = userPositions[positionId].owner;
    positionIDs[trader].removeItem(positionId);
}


// @audit called by `_removePosition`
function removeItem(uint256[] storage items, uint256 item) internal {
    uint256 index = getItemIndex(items, item);


    removeItemByIndex(items, index);
}


// @audit called by `removeItem`
function getItemIndex(uint256[] memory items, uint256 item) internal pure returns (uint256) {
    uint256 index = type(uint256).max;


    // @audit OOG revert for large items.length
    for (uint256 i = 0; i < items.length; i++) {
        if (items[i] == item) {
            index = i;
            break;
        }
    }


    return index;
}

A malicious user can exploit this for loop by opening multiple small positions and ensuring the last one becomes liquidatable. When a liquidator tries to process that position, the transaction will fail due to running out of gas, making liquidation impossible.

Mitigation:

  • Enforce a minimum position size to prevent excessive small “dust” positions.
  • Use a mapping or data structure that avoids iterating through all positions.

Guideline: Does the protocol iterate over an unbounded list that users can continuously add to? Is there a minimum position size enforced?

Additional cases: [1, 2, 3]

Attacker uses multiple positions to prevent liquidation

In some protocols, a user's health score is calculated based on all their open positions combined. If their overall health score falls below a threshold, all positions are liquidated in a single transaction.

Consider this code example:

// load open markets for account being liquidated
ctx.amountOfOpenPositions = tradingAccount.activeMarketsIds.length();


// iterate through open markets
for (uint256 j = 0; j < ctx.amountOfOpenPositions; j++) {
    // load current active market id into working data
    // @audit assumes constant ordering of active markets
    ctx.marketId = tradingAccount.activeMarketsIds.at(j).toUint128();


    // snip - a bunch of liquidation processing code //


    // remove this active market from the account
    // @audit this calls `EnumerableSet::remove` which changes the order of `activeMarketIds`
    tradingAccount.updateActiveMarkets(ctx.marketId, ctx.oldPositionSizeX18, SD_ZERO);

Because EnumerableSet does not guarantee element order and its remove function uses a swap-and-pop method for efficiency, removing an active market that is not the last in the list can disrupt the order of a user’s active markets.

A malicious user can exploit this by opening multiple positions and triggering this corruption, causing liquidation attempts to fail with an array out-of-bounds error.

Mitigation: Iterate over a memory copy of activeMarketIds using EnumerableSet::values instead of accessing storage directly.

Guideline: Can a user with multiple open positions be liquidated? Does the test suite include a test for this scenario?

Attacker front-runs to prevent liquidation

If a liquidatable user can modify key variables during the liquidation process, they can force the transaction to revert. By front-running liquidation attempts, they make themselves impossible to liquidate.

Examples of blocking liquidation:

Guideline: Is there a user-controlled variable that can cause liquidation to revert? Can a liquidatable user front-run liquidation to exploit this? What actions can a liquidatable user take, and should they be able to do so?

Additional cases: [1, 2, 3, 4, 5, 6]

Attacker uses pending actions to block liquidation

Consider this liquidation check:

require(balance - (withdrawalPendingAmount + depositPendingAmount) > 0);

A malicious user can exploit this by initiating a withdrawal equal to their balance, causing all liquidation attempts to fail and making liquidation impossible.

Mitigation: Restrict liquidatable users from performing certain actions like deposits, withdrawals, or swaps. However, this approach may unintentionally affect legitimate users who had pending withdrawals before becoming liquidatable.

Additionally, an attacker might exploit protocol functions while in a liquidatable state to profit from an upcoming liquidation. Protocols should carefully assess which actions a liquidatable user should be allowed to perform.

Guideline: Are there any actions that require multiple transactions over several blocks to complete? If so, what occurs if a user is liquidated while these actions are still pending? What actions can a liquidatable user take, and should they be allowed to perform them?

Additional cases: [1, 2]

Attacker uses malicious onERC721Received callback to block liquidation

If an NFT (ERC721) is "pushed" to an attacker-controlled address during liquidation, the attacker can set up their contract to revert in the onERC721Received callback, preventing liquidation from being completed.

Mitigation: Use a "pull" mechanism, requiring NFT owners to manually retrieve their tokens in a separate transaction.

This attack can also occur with ERC20 tokens with transfer hooks, potentially disrupting liquidation settlements.

Guideline: If liquidation relies on a "push" mechanism for token transfers, can an attacker exploit callbacks to force the transaction to revert?

Attacker uses yield vault to evade collateral seizure during liquidation

Some multi-collateral protocols allow users to deposit collateral into vaults or farms that generate yield, maximizing capital efficiency. These protocols must properly account for both deposited collateral and earned yield when:

  • calculating the minimum collateral required to prevent liquidation
  • seizing collateral and generated yield during liquidation

If only the first is implemented, an attacker can exploit this by:

  1. borrowing against their deposited collateral
  2. allowing liquidation to occur
  3. withdrawing both their collateral and earned yield from the vault or farm

Mitigation: Smart contract auditors should verify that all instruments used as collateral are accounted for in liquidation and that any contracts holding collateral are properly notified when liquidation occurs.

Guideline: Does the protocol include features like yield generation for deposited collateral? If so, is the liquidation code fully integrated with these features? Can users obscure their deposited collateral in ways the liquidation system does not recognize?

Additional cases: [1]

Liquidation fails if bad debt exceeds insurance fund

In protocols where an insurance fund covers bad debt, liquidation transactions will fail if the debt surpasses the fund’s available balance—unless the protocol includes specific handling for this scenario. This can leave large insolvent positions stuck indefinitely, preventing liquidation until enough fees accumulate to replenish the insurance fund.

Guideline: What happens if the bad debt from an insolvent position exceeds the insurance fund’s balance?

Additional cases: [1]

Liquidation fails due to insufficient funds from a fixed liquidation bonus

Consider this code, which attempts to guarantee a fixed 10% liquidation bonus by providing extra seized collateral to the liquidator:

uint256 tokenAmountFromDebtCovered = getTokenAmountFromUsd(collateral, debtToCover);
// liquidator always receives 10% bonus
uint256 bonusCollateral = (tokenAmountFromDebtCovered * LIQUIDATION_BONUS) / LIQUIDATION_PRECISION;
_redeemCollateral(collateral, tokenAmountFromDebtCovered + bonusCollateral, user, msg.sender);

When a borrower’s collateral ratio falls below 110%, they become under-collateralized and subject to liquidation. However, if the fixed liquidation bonus requires more collateral than what remains, the transaction will fail, preventing liquidation.

Mitigation: Check whether the borrower has enough collateral to cover the bonus and, if not, limit the bonus to the maximum available amount.

Guideline: What happens if there isn’t enough collateral to fully cover the liquidation bonus?

Additional cases: [1, 2]

Liquidation fails for non-18 decimal collateral

Multi-collateral protocols support various assets, some of which do not follow the standard ERC20 18-decimal precision. To handle this, protocols typically:

  • use 18 decimals for internal calculations and storage
  • apply native token decimals when transferring assets
  • accept native token decimals in user-facing function inputs

While effective when consistently applied, large protocols with multiple developers can introduce inconsistencies. Auditors should verify that liquidation functions properly when either the collateral or debt token has a different decimal precision.

Guideline: Does liquidation work correctly when tokens have varying decimal precision?

Liquidation fails because of multiple nonReentrancy modifiers

In complex protocols, liquidation logic often involves optional calls to multiple contracts. Auditors should ensure that no execution path triggers two functions with the nonReentrant modifier within the same contract, as this would cause the transaction to fail.

Guideline: Are there any liquidation paths that invoke multiple nonReentrant modifiers within the same contract?

Liquidation fails due to zero-value token transfers

Liquidation code typically involves calculating various token amounts, such as liquidator rewards and fees, followed by multiple token transfers. If the protocol does not check for zero-value transfers, liquidation may fail when dealing with tokens that revert on such transactions.

Guideline: Does the protocol perform zero-value checks before token transfers? If not, does it support tokens that revert on zero-value transfers?

Additional cases: [1]

Liquidation reverts from token deny list

Some tokens, like USDC, have deny lists that allow token admins to freeze certain addresses, causing all transfer attempts to those addresses to revert. Many liquidation mechanisms use a "push" model, where tokens are automatically sent to designated addresses. If a protocol supports deny-listed tokens and liquidation attempts to send funds to a blocked address, the transaction will fail, making liquidation impossible.

Mitigation: Switch to a "pull" model, where users must manually claim their tokens instead of having them automatically sent.

Guideline: Does the protocol use a "push" mechanism for liquidation and support tokens with deny lists? If so, what happens when liquidation attempts to send tokens to a blocked address?

Additional cases: [1, 2, 3]

Impossible to liquidate when only one borrower

Consider this liquidation logic:

// get number of borrowers
uint256 troveCount = troveManager.getTroveOwnersCount();


// only process liquidations when more than 1 borrower
while (trovesRemaining > 0 && troveCount > 1) {

This code prevents liquidation if there is only one borrower, which is a design flaw. A single borrower should still be subject to liquidation if their position becomes liquidatable.

Guideline: If only one borrower remains, can they still be liquidated?

Additional cases: [1]

Incorrect liquidation calculations

Liquidation involves multiple calculations, including collateral valuation, bad debt assessment, and determining liquidator rewards and fees. Even minor miscalculations can have severe consequences.

Incorrect calculation of liquidator reward

Liquidation often involves both debt and collateral tokens, which may have different decimal precisions. Errors in handling these differences can lead to:

  • rewards being too low, discouraging liquidators from participating
  • rewards being too high, overcompensating liquidators at the protocol’s expense

Consider this simplified code:

function executeLiquidate(State storage state, LiquidateParams calldata params)
    external returns (uint256 liquidatorProfitCollateralToken) {


    // @audit debtPosition = USDC using 6 decimals
    DebtPosition storage debtPosition = state.getDebtPosition(params.debtPositionId);


    // @audit assignedCollateral = WETH using 18 decimals
    uint256 assignedCollateral = state.getDebtPositionAssignedCollateral(debtPosition);


    // @audit debtPosition.futureValue = USDC using 6 decimals
    //        debtInCollateralToken = WETH using 18 decimals
    uint256 debtInCollateralToken = state.debtTokenAmountToCollateralTokenAmount(debtPosition.futureValue);


    if (assignedCollateral > debtInCollateralToken) {
        uint256 liquidatorReward = Math.min(
            assignedCollateral - debtInCollateralToken,
            // @audit liquidatorReward calculated using debtPosition.futureValue using
            // 6 decimals instead of debtInCollateralToken using 18 decimals, even though
            // liquidation reward is paid in WETH collateral which uses 18 decimals 
            Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
            // @audit should be:
         // Math.mulDivUp(debtInCollateralToken, ...)


        );

This liquidation function distributes rewards in collateral tokens (WETH with 18 decimals) but calculates the reward using the debt token (USDC with 6 decimals). As a result, liquidators receive a significantly lower payout than expected, reducing their incentive to participate.

The liquidation reward should scale proportionally—if a borrower takes the same loan amount from three lenders in one account, their liquidation reward should be comparable to borrowing from three lenders using separate accounts.

Errors in liquidation reward calculations can arise in many ways. Since there is no universal guideline, auditors must carefully review the specific implementation to identify potential miscalculations.

Additional cases: [1, 2, 3, 4, 5, 6, 7, 8, 9]

Failure to prioritize liquidation reward

During liquidation, multiple fees may need to be distributed to different entities. If the available collateral (or insurance fund in cases of bad debt) is insufficient to cover all fees, the protocol should prioritize paying the liquidator’s reward. This ensures liquidators remain incentivized, especially in trustless systems where they play a crucial role in maintaining protocol solvency.

Guideline: What happens if there isn’t enough collateral to cover all liquidation fees? Is the liquidator reward given priority, particularly in protocols that rely on trustless liquidators?

Incorrect calculation of protocol liquidation fee

Some protocols impose a "protocol fee" during liquidation, paid either by the liquidator or the liquidated user. If miscalculated and set too high, it can make liquidations unprofitable, discouraging liquidators and allowing bad debt to accumulate.

Consider this protocol liquidation fee calculation code:

function _transferAssetsToLiquidator(address position, AssetData[] calldata assetData) internal {
    // transfer position assets to the liquidator and accrue protocol liquidation fees
    uint256 assetDataLength = assetData.length;
    for (uint256 i; i < assetDataLength; ++i) {
        // ensure assetData[i] is in the position asset list
        if (Position(payable(position)).hasAsset(assetData[i].asset) == false) {
            revert PositionManager_SeizeInvalidAsset(position, assetData[i].asset);
        }
        // compute fee amt
        // [ROUND] liquidation fee is rounded down, in favor of the liquidator
        // @audit liquidator fee calculated from seized collateral amount
        //         makes many liquidations unprofitable
        uint256 fee = liquidationFee.mulDiv(assetData[i].amt, 1e18);


        // transfer fee amt to protocol
        Position(payable(position)).transfer(owner(), assetData[i].asset, fee);
        // transfer difference to the liquidator
        Position(payable(position)).transfer(msg.sender, assetData[i].asset, assetData[i].amt - fee);
    }
}

In this case, the protocol liquidation fee is set as a percentage of the total seized collateral. This approach can make many liquidations unprofitable—such as when the fee is excessively high (e.g., 30% of seized collateral), discouraging liquidators from acting and allowing bad debt to accumulate.

Mitigation:

  • Eliminate the protocol liquidation fee or replace it with a small flat fee.
  • Calculate the fee as a percentage of the liquidator’s actual profit rather than the total seized collateral.

Incorrect fee calculations can also work in reverse, allowing liquidators to pay less than necessary to complete a liquidation.

Guideline: Is the protocol liquidation fee based on the liquidator’s profit? If not, could the fee structure make liquidation unprofitable, discouraging liquidators?

Additional cases: [1, 2, 3, 4, 5]

Liquidation fees not included in minimum collateral requirement

When determining if a position is solvent or setting minimum collateral requirements, liquidation fees should be factored in. If they are excluded, a position that appears solvent may not have enough collateral to cover liquidation costs, causing the transaction to fail or creating bad debt.

However, some protocols do not include liquidation fees in collateral calculations, arguing it may be unfair to users.

Guideline: Are liquidation fees accounted for in minimum collateral requirements? If not, has the protocol explicitly documented its reasoning?

Unfair liquidation due to earned yield not included in collateral value

Some protocols allow deposited collateral to generate yield, increasing capital efficiency. However, if the earned yield is not included in the collateral valuation, a user may be unfairly liquidated despite having sufficient total value.

Guideline: Is earned yield considered when calculating a user's total collateral value? If not, can this lead to unfair liquidation? If it is included, what happens to the earned yield during liquidation—is it retained or lost?

Unfair liquidation due to positive PNL not included in collateral value

In leveraged trading protocols, a trader’s open profit and loss (PNL) should be factored into their total collateral value:

  • Negative PNL should always be deducted, ensuring liquidation happens sooner when losses occur.
  • Positive PNL should be added to collateral value, delaying liquidation when a trader is in profit.

If a protocol ignores positive PNL but deducts negative PNL, traders with large unrealized gains may be unfairly liquidated. If a protocol chooses not to account for positive PNL, it should explicitly communicate this to users.

Guideline: Is open PNL considered when determining liquidation? If not, can traders be unfairly liquidated despite having profitable positions? If it is included, what happens to the unrealized PNL—does it carry over, or is it lost?

Unfair liquidation after L2 sequencer grace period

Chainlink recommends a grace period after an L2 sequencer restarts, during which price data should not be fetched. If protocols prevent users from depositing additional collateral during this period, they may face immediate liquidation once fresh price data is available.

Mitigation: Protocols should consider allowing collateral deposits during the grace period, enabling users to protect their positions before new price data is applied.

Guideline: After an L2 sequencer comes back online, can users deposit collateral during the grace period before price data updates? Does the protocol provide a grace period to prevent immediate liquidation, or are users liquidated as soon as price data resumes?

Unfair liquidation due to interest accumulation during protocol pause

If a protocol allows pausing but prevents users from repaying loans while paused, borrow interest should also stop accumulating. Otherwise, users may face instant liquidation upon unpausing due to the sudden interest buildup.

Guideline: Does borrow interest continue to accrue while the protocol is paused, even when users cannot make repayments?

Unfair liquidation due to repayment pause while liquidations remain active

A protocol should not allow a scenario where repayments are paused while liquidations remain enabled. This would unfairly liquidate borrowers who intended to repay but were blocked by the protocol itself.

Mitigation: Protocols should implement a grace period after unpausing liquidations, allowing users time to repay loans or add collateral before being liquidated.

Guideline: Can the protocol enter a state where users cannot repay but still face liquidation? After unpausing, is there a grace period for repayments and collateral deposits, or are users liquidated immediately?

Additional cases: [1, 2, 3, 4, 5, 6]

Late liquidation due to isLiquidatable not refreshing interest or funding fees

Before determining if a user is liquidatable, the protocol must first update any accrued fees, such as loan interest or funding fees in leveraged trading. Failure to do so can result in delayed or incorrect liquidations.

Auditors should pay close attention to view functions that check liquidation status but do not modify state, ensuring they properly calculate the latest owed fees. Additionally, all relevant fees must be updated before executing liquidation.

Guideline: Does the protocol refresh all interest, yield, funding fees, and PNL before determining whether a user is liquidatable?

Positive PNL, yield, and rewards lost during liquidation

An edge case in leveraged trading occurs when:

  1. A trader deposits collateral $C and opens a leveraged long position on asset $A.
  2. The price of $A rises, giving the trader significant unrealized profit.
  3. The price of $C drops even further, making the position liquidatable despite the trader being in overall profit.
  4. Alternatively, the accumulated borrow or funding fees exceed the unrealized profit, triggering liquidation.

If this happens, the trader’s positive PNL, earned yield, and other rewards should be credited to their account before liquidation. Otherwise, these assets are lost, making liquidation unfair.

Guideline: Does the protocol ensure all unrealized profit, yield, and rewards are realized before liquidation? If not, are they lost after liquidation?

Additional cases: [1, 2, 3]

No swap fee applied during liquidation

Some protocols impose swap fees when converting one asset to another. If these fees apply to regular swaps but are not charged when a liquidator exchanges debt tokens for seized collateral, the protocol, and its insurance fund may accumulate fewer tokens than expected.

Guideline: Does the protocol typically charge swap fees and execute swaps during liquidation? If so, does it apply a swap fee to liquidation swaps? If not, has the protocol explicitly documented why this exception exists?

Profitable self-liquidation using oracle update manipulation

An attacker can exploit a user-triggered oracle update to execute a profitable self-liquidation by following these steps:

  1. Use a flash loan to acquire a large amount of collateral tokens.
  2. Deposit the collateral and borrow the maximum allowable debt (max leverage).
  3. Trigger an oracle price update.
  4. Liquidate their own position.

This attack becomes profitable if the oracle price update causes the full collateral balance to be recovered while repaying fewer debt tokens than were borrowed.

Mitigation:

  • Apply borrow and liquidation fees to reduce profit incentives.
  • Introduce a cool-off period before liquidation can occur.
  • Restrict leverage for highly volatile collateral assets.
  • Use oracles with smaller price deviations that cannot be triggered by users.

Self-liquidation can be a significant risk, especially if users can deliberately make themselves liquidatable to exploit the protocol.

Guideline: Can users deliberately become liquidatable and trigger self-liquidation? Can oracle price updates be manipulated to extract value from the protocol?

Liquidation leaves borrower with lower health score

Liquidation, whether full or partial, should improve the borrower’s financial position by reducing their risk of future liquidation. However, in protocols that support multiple collateral types and partial liquidation, subtle bugs may leave borrowers in a worse condition post-liquidation, increasing the likelihood of further liquidations.

Consider this liquidate function:

function liquidatePartiallyFromTokens(
    uint256 _nftId,
    uint256 _nftIdLiquidator,
    address _paybackToken,
    address _receiveToken, //@audit liquidator can choose collateral
    uint256 _shareAmountToPay
)

This liquidate function allows the liquidator to select which collateral to seize as compensation. This is risky because different types of collateral have varying:

  • borrowing factors, which determine how much can be borrowed against them
  • risk profiles, where some assets (e.g., USDC) are stable, while others (e.g., ETH or speculative ERC20 tokens) are highly volatile

A liquidator can exploit this by first seizing the borrower’s most stable, high-borrowing-factor collateral. This leaves the borrower with:

  1. More volatile collateral that is subject to larger price swings
  2. A lower borrowing factor, making them more likely to become liquidatable again

This can lead to cascading liquidations, where the first liquidation weakens the borrower’s position, triggering additional liquidations in quick succession. This leaves the trader with an unhealthier and riskier collateral basket.

Mitigation:

  • Calculate the borrower’s health score before and after liquidation.
  • If the post-liquidation health score is equal to or lower than before, revert the transaction.

Common health score calculations:

  • Collateral-to-debt ratio: collateral_value / borrow_value
  • Borrowing power: (collateral_value * loan_to_value_ratio) / borrow_value

Guideline: Can liquidation leave a borrower in a worse financial state? Can liquidators selectively seize collateral, leaving borrowers with a more volatile and riskier portfolio?

Additional cases: [1, 2, 3, 4]

Corruption of collateral priority order

Protocols allowing various collateral types can lessen the aforementioned risk by establishing a prioritized liquidation order, targeting the more volatile, higher-risk collateral first. However, caution is crucial when designing functions that modify this order, as they could inadvertently corrupt it, leading to improper liquidation.

Guideline: Can the collateral liquidation priority be corrupted by the functions that change it?

Incorrect repayment attribution due to borrower replacement

Certain protocols offer an optional "replacement" liquidation method. This allows a liquidatable position to be used to fulfill an order from an order book, essentially substituting a financially unstable borrower with a stable one.

Other protocols permit users to "purchase" a liquidatable position, effectively assuming it, provided they have sufficient collateral to ensure its solvency. This achieves the same outcome: transferring the debt from the original, unhealthy borrower to a new, healthy one.

In both scenarios, the borrower's address is updated to the new borrower, but other data, like the position's id and debt, typically remain unchanged. Consider the potential conflict when these two transactions occur simultaneously:

Transaction 1 (TX1): The original, liquidatable borrower attempts to repay their position, providing its id as input.

Transaction 2 (TX2): A replacement liquidation transaction attempts to transfer the position to a healthier borrower.

If TX2 executes before TX1, the new borrower acquires the position before the original borrower's repayment. The original borrower effectively repays the debt of the new borrower! In protocols allowing users to "buy" positions, a malicious actor could exploit this by front-running repayment transactions. They could buy the liquidatable position and then have the original borrower repay the debt on the position they just acquired.

A possible solution is to require repayment transactions to include the borrower's address as input and to revert the transaction if the current borrower's address doesn't match.

Guideline: Is it possible to transfer a liquidatable position from an unstable user to a stable one? If so, what are the consequences if the unstable user attempts repayment concurrently with the transfer?

Additional cases: [1]

No gap between borrow and liquidation loan-to-value ratio

Many platforms utilize a higher loan-to-value (LTV) ratio for initiating a loan compared to the LTV ratio that triggers liquidation. This difference is implemented to protect borrowers from immediate liquidation after establishing a new position.

Eliminating this gap between borrowing and liquidation LTV ratios allows users to open positions that are dangerously close to liquidation. This increases the probability of liquidations, jeopardizing the platform's stability and negatively impacting user experience.

Guideline: Can a user face liquidation shortly after opening a new position?  What about after adjusting an existing position?

Additional cases: [1, 2, 3, 4]

Borrower accrues interest while liquidation auction running

Certain protocols employ an "auction" system where a liquidatable position is auctioned off over a specific timeframe.  In these instances, debt interest accrual should be suspended immediately upon auction initiation. Liquidating positions shouldn't accumulate further interest during the auction.

Guideline: Does a borrower continue to accrue interest while their debt is being auctioned?

No slippage on liquidation and swaps

Ideally, during liquidation, the liquidator should be able to define the minimum acceptable reward (tokens, shares, etc.). This is crucial for protocols using liquidation swaps, as these swaps are susceptible to MEV exploitation, potentially yielding lower-than-expected rewards for the liquidator. 

Guideline: Can the liquidator set a slippage parameter? If swaps are part of liquidation, could this lead to the liquidator (or the protocol or the liquidated user) receiving fewer tokens than anticipated?

Secure your protocol today

Join some of the biggest protocols and companies in creating a better internet. Our security researchers will help you throughout the whole process.
Stay on the bleeding edge of security
Carefully crafted, short smart contract security tips and news freshly delivered every week.