Crypto OS
Technical Crypto OSStage 4 · DeFi Engineering

T22 · Lending Protocol

When must liquidation outrun the market?

Capabilities practiced
BuilderFinancial Literacy
Hands on
Implement a minimal lending pool and liquidation function; create a price crash and check whether liquidation completes in time.
AI Lab
Have AI design an interest-rate curve. Simulate utilization rising from 50% to 99% yourself.

A Real-World Problem

Your lending pool has been live for a month and appears to work well.

You are proudest of its interest-rate curve: two linear segments, a carefully placed kink, sharp borrowing costs at high utilization, and pleasing symmetry between deposit and borrow returns on the chart. You wrote extensive unit tests and checked each slope to six decimal places.

Then, one afternoon, the collateral asset falls 25% in twenty minutes.

During reconciliation the next day, you find bad debt: after a position was liquidated, the collateral sale could not cover its debt. The protocol absorbs the shortfall.

You review the components one by one:

  • Was health calculated incorrectly? No; hand calculation matches the code.
  • Was there a bug in liquidation? No; tests exercised it hundreds of times.
  • Did the price feed fail? No; all four T21 checks passed, and prices accurately tracked the decline.
  • Was the collateral factor too high? It was 75%, a common setting.

Every component worked. Bad debt still appeared.

The chain history reveals the cause: the position became liquidatable at 14:32, but the first successful liquidation executed at 14:36.

Four minutes. During those minutes, the price fell another 12%.

You spent a month polishing the interest-rate curve, but those four minutes decided whether the protocol was safe.

Thought Experiment

Think of a lending protocol as a pawnshop. Set interest rates aside.

The rule is simple: pledge an item worth 100 and borrow 75.

The item's price can change. At 80, the pawnshop may still be safe; at 70, it loses money.

The owner cannot watch every pledged item around the clock. Instead, they outsource the task to the market and post a notice:

Anyone who finds collateral worth less than 80 may buy it at a 5% discount to its current value, using the proceeds to repay its loan.

Notice who these people are: they are not employees, receive no salary, and owe no duty to act. They appear only when it is profitable.

Break the mechanism into four questions:

First, why would they come? The 5% discount is the entire incentive. If the item is worth only 200, that is 10 in gross profit; after fees and transaction costs, the trip may not pay.

Second, how quickly can they arrive? They must notice the position, prepare funds (hold or borrow the 190), send a transaction, and wait for inclusion.

Third, can another buyer win first? Everyone sees the same opportunity; only one captures it. Losers pay for unsuccessful attempts and may bid more next time. Those costs eventually reach users.

Fourth, and most important, what is the 25% buffer really?

It is not merely a safety cushion. It is a time budget.

Precisely, the collateral may fall by at most 25% between the start of the decline and completion of liquidation. If price falls faster or liquidators arrive later, the pawnshop bears the gap.

The real question is not “what collateral factor should we set?” It is whether price falls faster than liquidation can run.

Your Decision

The market is falling fast and many positions approach their liquidation lines. As protocol designer, which of four levers do you use first?

What Happens

All four levers buy the same thing: time.

LeverWhat it buysWho paysSide effect
Lower collateral factorMore room for price declinesAll borrowers through lower efficiencyExisting positions may suddenly liquidate
Raise liquidation bonusFaster liquidator responseLiquidated usersMore aggressive liquidation
Partial liquidationMore tolerance for usersProtocol through slower responseSmall positions may go untouched
Rising bonusMore precise incentive pricingLiquidators and users share costBuilt-in delay

Summarize it as the chapter's central inequality:

Bad debt appears when:

    Collateral price decline during the "liquidation delay"
                >
    Buffer provided by the liquidation threshold + portion consumed by liquidation bonus

Where:
  Buffer = 1 - debt / (collateral value * liquidation threshold) ← what you can design
  Liquidation delay = detection + decision + funding + inclusion   ← what you cannot control

The left side is market speed and the right side is your design. Safety depends on which is larger.

Most people focus on the right: tuning parameters, optimizing formulas, and proving the rate curve continuous. Incidents tend to come from the left, driven by how violently price moves and how slowly liquidation completes.

Hence this chapter's rule: a lending protocol's safety boundary is liquidation speed, not an elegant interest-rate model.

The rate model determines income. Liquidation speed determines survival.

Build a Model

Split liquidation delay into stages so you can improve the right one:

  1. Price falls
  2. Position crosses the threshold
  3. Someone detects it
  4. They fund and submit a transaction
  5. Transaction is included
  6. Liquidation finishes
The time from crossing the threshold to completion is liquidation delay. Measure and improve each part separately.
StageTypical timeWho can improve itHow
Price appears onchainDepends on oracle heartbeatProtocolT21 parameter choices
Someone detects itMilliseconds to minutesEcosystemEvents and open query APIs
Funds are preparedNear zero when borrowing is possibleProtocolAllow funding within the liquidation transaction
Transaction is includedOne to several blocksLiquidatorHigher gas bid
Failed competitors retryMay exceed all previous stages combinedProtocolPartial liquidation and bonus design

The final row is easily missed and can explode during congestion. Ten liquidators race for one opportunity; nine fail, recompute, and submit again as prices keep falling.

Now the code: three central functions.

First, the health factor. It is the protocol's pulse:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Express ratios in basis points (bps) to avoid floating point
uint256 constant BPS = 10_000;

struct Account {
    uint256 collateral;   // Collateral amount
    uint256 debt;         // Debt principal, with interest already accrued through the index
}

/// Health factor >= 1 means safe; below 1 means liquidatable
/// Return value is scaled by BPS as well: 10_000 represents 1.0
function healthFactor(
    uint256 collateral,
    uint256 price,              // Unit price of collateral
    uint256 liquidationLtv,     // Liquidation threshold in bps; often 7000–8500, per-asset configurable
    uint256 debt
) pure returns (uint256) {
    if (debt == 0) return type(uint256).max;
    uint256 adjusted = collateral * price * liquidationLtv / BPS;
    return adjusted * BPS / debt;
}

Two details:

  • Liquidation threshold and collateral factor are different numbers. The factor caps borrowing; the threshold triggers liquidation and must be higher. Their gap is the user's buffer and only opportunity to repair the position.
  • Accrue interest before calculating health. A long-idle position can cross the line from interest alone, without any price change.

Second, the interest-rate model. Two slopes are common:

/// Utilization = total borrowed / total supplied
function utilization(uint256 borrowed, uint256 supplied) pure returns (uint256) {
    if (supplied == 0) return 0;
    return borrowed * BPS / supplied;
}

/// Gentle before the kink; steep afterward
function borrowRate(
    uint256 u,           // Utilization, in bps
    uint256 base,        // Base rate
    uint256 slope1,      // Slope before kink
    uint256 slope2,      // Slope after kink, often an order of magnitude higher
    uint256 kink         // Kink, often 8000–9000 bps; configurable
) pure returns (uint256) {
    if (u <= kink) {
        return base + u * slope1 / kink;
    }
    uint256 excess = u - kink;
    return base + slope1 + excess * slope2 / (BPS - kink);
}

The steep segment after the kink is not mainly for revenue. It protects liquidity. Near 100% utilization, depositors cannot withdraw. A sharp borrowing rate pushes borrowers to repay and restore availability.

This is rate limiting through a price. It works only if the slope is steep enough and borrowers can actually repay promptly.

Third, liquidation. This is the chapter's most delicate function:

error NotLiquidatable(uint256 health);
error TooMuchDebt();

function liquidate(
    address user,
    uint256 repayAmount        // Debt the liquidator repays for the user
) external {
    Account storage a = accounts[user];
    _accrueInterest(user);

    uint256 price = priceGuard.readPrice();          // Includes the four T21 checks
    uint256 health = healthFactor(a.collateral, price, liquidationLtv, a.debt);
    if (health >= BPS) revert NotLiquidatable(health);

    // Maximum debt repaid in one call; closeFactor often around 5000 bps, configurable
    uint256 maxRepay = a.debt * closeFactor / BPS;
    if (repayAmount > maxRepay) revert TooMuchDebt();

    // Collateral seized = equivalent value plus bonus
    uint256 seized = repayAmount * BPS / price;
    seized = seized * (BPS + liquidationBonus) / BPS;

    // Insufficient collateral: this is where bad debt arises
    if (seized > a.collateral) {
        seized = a.collateral;
        // Record remaining debt explicitly; do not silently erase it
        _recordBadDebt(user, a.debt - repayAmount);
    }

    a.debt -= repayAmount;
    a.collateral -= seized;

    _pullDebtToken(msg.sender, repayAmount);
    _pushCollateral(msg.sender, seized);
}

The final if is where bad debt appears. Write it explicitly and account for it.

If you omit it and simply revert when collateral is insufficient, the position can never be liquidated. It remains in the protocol, debt keeps accruing, and collateral is always short. A known loss turns into a growing black hole.

General rule: bad debt cannot always be avoided, but it must be visible. Recorded losses can be covered by reserves, shared with depositors, or disclosed. Hidden losses erupt when someone cannot withdraw.

What It Is Called

Collateral Factor / LTVCollateral factor / loan-to-value

The maximum share of collateral value that may be borrowed.

It differs from the liquidation threshold: the factor limits opening debt, while the threshold triggers closing. Their gap is the user's only rescue window. Reduce it to zero and every new position sits at the liquidation line.

Health FactorHealth factor

Collateral value adjusted by the liquidation threshold, divided by debt.

Above 1 is safe; below 1 is liquidatable. It is a ratio, not an amount. Two positions with the same health can cause very different losses if one is much larger, so monitor both dimensions.

Utilization / KinkUtilization / kink

The fraction of deposits that have been borrowed, and the point where the rate curve becomes steep.

The steep segment is a liquidity safeguard: it prices borrowing high enough to encourage repayment and preserve withdrawals. Judge a rate curve not by the gentle part but by its response near 100% utilization.

Liquidation BonusLiquidation bonus

The discounted portion of collateral that a liquidator receives.

It is the reason liquidation occurs. Too low and nobody acts; too high and users are over-penalized while small fluctuations attract aggressive liquidations. It prices liquidation speed.

Close FactorClose factor

The maximum share of a position's debt that one liquidation can repay.

It balances user protection against protocol protection. A low cap is gentler but thins liquidator profit and slows response; many designs loosen it in extreme markets.

Bad DebtBad debt

Debt left uncovered after liquidation.

The danger is not only its size, but whether it is recorded. Visible bad debt can be absorbed by reserves or allocated transparently. Hidden bad debt can start a run, with everyone trying to withdraw before the pool is empty.

Hands-on

Hands onBuild a minimal lending pool and liquidation function; simulate a price crash and measure whether liquidation finishes in timeSolidity and any contract test frameworkFree, using only a local network

Build a minimal pool. Use one collateral asset, one borrowed asset, and five functions: supply, borrow, repay, withdraw, and liquidate.

Accrue interest through an index instead of updating each account continuously. Maintain one global cumulative index advanced when actions occur; store each user's debt in units adjusted by the index at opening. Interest computation stays O(1) regardless of user count.

Use T21's PriceGuard as the source, without removing any of its four checks.

Test set one: arithmetic.

  • Deposit 100 units at price 100 with a collateral factor of 7,500; borrowing 7,500 is allowed but 7,501 must fail.
  • Advance time thirty days and compare accrued debt with a hand calculation.
  • Test health at the boundary: exactly 1 is not liquidatable; only a value below 1 is.

That boundary matters. Greater than or equal versus strictly greater decides the fate of positions right on the line.

Test set two: simulate a crash.

Use a stub oracle with controlled prices and play this sequence:

Block 0    Price 100; health factor 1.33
Block 1    Price 92
Block 2    Price 85    ← position crosses the liquidation line
Block 3    Price 79
Block 4    Price 74
Block 5    Price 70

Do not liquidate immediately at block 2. Execute separately at blocks 3, 4, and 5, recording:

Liquidation atLiquidator receivesUser keepsProtocol bad debt
Block 3 (1-block delay)
Block 4 (2-block delay)
Block 5 (3-block delay)

Find the first row with bad debt. It marks the maximum delay this configuration tolerates.

Test set three: nobody comes to liquidate.

Shrink the position to a tiny size and repeat. Calculate liquidator net profit:

Liquidator profit = liquidated amount * liquidation bonus
                    - gas cost - slippage when selling collateral

A rational liquidator will not act when this is zero or negative

Calculate the crossover position size. Smaller positions cannot be economically liquidated in your protocol; they become dust bad debt over time.

Now increase gas price tenfold and calculate again. Observe the new crossover. This explains why bad debt clusters during congestion.

Quantitative exercise: chart the failure boundary for your parameters.

Combine earlier results in a two-dimensional table. Put liquidation delay in blocks on one axis and price decline during that delay on the other:

             1-block delay   3-block delay   10-block delay
5% fall      Safe            Safe            Safe
15% fall     Safe            Safe            Bad debt
30% fall     Safe            Bad debt        Bad debt
50% fall     Bad debt        Bad debt        Bad debt

Generate your own actual table; the boundary depends on collateral factor, liquidation threshold, and bonus.

Then ask three questions:

  1. What was the asset's largest historical one-hour decline?
  2. During such periods, how high is onchain gas and how long does liquidation take?
  3. Where do those two figures land in your table?

If they land in “bad debt,” change your parameters, probably somewhere other than the interest-rate curve.

Use only a local network, never real funds. Real funds enter only the capstone after Stage 5 security acceptance.

AI Lab

AI LabAsk AI to design a rate curve; simulate utilization climbing from 50% to 99% yourselfLevel 2 · AI Copilot

Ask in three steps:

Step one:
Design a two-slope interest-rate curve for a lending pool. Give parameters,
formula, and Solidity code. Explain where to put the kink, why, and how steep
the post-kink segment must be to matter.

Step two:
Suppose utilization rises from 50% to 99% in two hours. Simulate it step by step:
borrow and deposit rates, borrowers' incentive to repay, and depositors'
incentive to supply at each point. When should market forces turn it around?

Step three:
What happens if utilization reaches 100% and stays there? Describe the
conditions for depositors, borrowers, and the protocol, plus each intervention
available to the protocol and its cost.

Step two is the Lab's value. Designing a curve is easy; showing why it repairs itself is hard. Press the model: when rates rise, will borrowers actually repay? What if their funds are locked in positions they cannot exit? If everyone repays at once, does buying the repayment asset push its price up?

Step three often exposes a missing idea: at 100% utilization depositors cannot withdraw, so a high quoted rate does them no immediate good. A paper yield is not spendable money. If that state persists, it can become a run.

After receiving the answer, plot the curve with actual numbers in a small script. Do not judge it only by how elegant the formula looks.

Verify these points yourself after using AI

  • Substitute 50%, 80%, 95%, 99%, and 100% into its curve; calculate borrow rates yourself and compare
  • At 99% utilization, is the rate high enough to induce immediate repayment? Merely “fairly high” is insufficient
  • Does the deposit rate subtract the protocol reserve share, or does it simply equal the borrow rate?
  • Is the curve continuous at the kink? Calculate the value from both sides
  • Did it note that depositors cannot withdraw at 100% utilization and ask whether the rate can keep rising?
  • Treat every specific parameter it gives as a configurable starting point, not a fact

Real-World Case

Congestion pushes liquidation transactions out of blocksRepeated volatile markets since 2020

In a crash, everyone trades at once and gas bids rise dramatically. Liquidations compete with all other transactions for inclusion, while their profit is capped by a few percentage points.

When liquidators are most needed, the economics may stop working. If gas exceeds profit, small positions go untouched, deteriorating until gas recedes and a shortfall has formed.

Liquidation delay is not constant. It rises with market stress, peaking precisely when it hurts most. Do not design parameters using calm-market delay measurements.

No auction bidders; collateral sells for almost nothingMultiple cases

Some protocols liquidate through auctions. Under extreme market or network conditions, nobody bids as the auction begins. Price keeps falling toward zero until someone takes the collateral for very little.

The collateral is gone but the debt remains, so the gap becomes bad debt.

Two lessons: auctions need a floor or minimum price, and more generally, any mechanism assuming someone will participate needs defined behavior when nobody does.

Utilization reaches 100% and depositors cannot withdrawPersistent risk

One asset is borrowed heavily, pushing utilization near 100%. A depositor's withdrawal fails because the pool has no liquid balance.

Rates rise sharply and should encourage repayment in theory. But a borrower who invested funds somewhere they cannot exit immediately cannot repay soon. The high rate simply makes debt grow faster.

The post-kink slope is meant to solve that problem, but may fail. Interest rates adjust with a lag and cannot unlock capital that is already trapped.

Dust positions accumulate over timeAll lending protocols

Many tiny positions cross the liquidation line, but each pays less bonus than the gas required, so none are liquidated.

They seem harmless at first but continue accruing interest, growing debt against perpetually insufficient collateral. Months later, all the dust adds up to substantial bad debt.

Common controls are a minimum borrow size and a batch liquidation entry point that handles many positions in one transaction, spreading gas across them.

Change One Variable

What ifRaise liquidation bonus from 5% to 15%

Response becomes faster, small positions become profitable again, and bad-debt risk falls.

There are two costs. Users visibly lose another 10% of collateral at liquidation. More subtly, liquidation becomes aggressive. A high enough reward gives liquidators reason to make positions cross the line themselves, perhaps by selling the collateral asset into the market. A role meant to handle events after the fact gains an incentive to cause them.

A sufficiently large reward changes the rewarded party's behavior. That pattern recurs throughout mechanism design.

What ifRaise collateral factor from 75% to 90%

Capital efficiency rises, borrowers find the protocol more attractive, and borrowing volume and revenue grow.

The time budget shrinks from 25% to 10%. Rebuild this chapter's two-dimensional table and many previously safe cells move into bad debt.

That is why high factors are usually reserved for low-volatility assets with deep liquidity. The upper limit is not merely risk appetite; it follows from volatility and liquidation delay, which you can estimate.

What ifMove to a chain with 0.4-second blocks that often congests

In theory, liquidation delay falls dramatically.

Watch two issues. During congestion, fast blocks can intensify competition: more liquidators chase the same opportunity and failed retries consume a larger share of the time. Every parameter measured in blocks also needs recalculation. “Liquidate within ten blocks” once meant two minutes and now means four seconds, which may not hold under load.

As in T21, derive time-related parameters again for a new chain instead of copying them.

What ifRaise the maximum close factor from 50% to 100%

A liquidator can handle the whole position in one call. Profit doubles, response quickens, and the protocol is safer.

The user experience worsens sharply. A brief price wick can take all a user's collateral even if the price recovers minutes later.

Many systems use tiers: normally cap repayment near half, but allow full liquidation below a much lower health level when the buffer is almost gone. This preserves user tolerance during ordinary volatility and emergency capacity in extremes.

Takeaway Questions

5
Who is paying?

Who pays? Borrowers pay interest, split between depositors and the protocol. Liquidated users also pay the liquidation bonus. Follow those three flows to understand the business model.

8
Who supplies liquidity?

Who supplies liquidity? Depositors do, but there is another kind here: liquidators provide immediately available buying power for collateral. If its market is too shallow, they cannot finish a liquidation. This is the same depth issue as T21.

9
Who bears the risk?

Who bears the risk? Bad debt falls on protocol reserves and depositors, not liquidators. Liquidators act only when profitable and have no obligation to serve during the worst market conditions. Design explicitly for their absence.

10
What is the incentive?

What is the incentive? One figure drives liquidation: bonus minus costs. To know whether liquidation will happen, calculate the liquidator's profit rather than relying on documentation.

Chapter Check

Key idea

The safety boundary of a lending protocol is liquidation speed, not the elegance of its interest-rate model.

Finished this chapter’s lab? Mark it completeView all progress

On this page