Lending and borrowing on-chain
A lending protocol lets users supply an ERC-20 token to a shared pool to earn yield, and lets others borrow from the same pool by posting collateral worth more than they borrow. The core shape has been stable since Compound v1 in 2018, a pool per asset, interest set by utilization, collateral priced by oracle, liquidations triggered when a position's collateral falls below a safety threshold. This lecture walks through each part of the system, how the parts connect, and where the design assumptions break.
The supply side
A supplier deposits asset X into the protocol's pool for X. In return they get a receipt token, typically called aToken on Aave or cToken on Compound. The receipt represents their share of the pool plus all interest accrued since they deposited.
This is the ERC-4626 vault pattern with one specific yield source. The yield comes from borrowers paying interest. When a borrower repays, their interest payment goes into the pool. The pool grows. Every supplier's receipt-token-to-asset conversion rate grows accordingly.
A supplier can redeem their receipt tokens for the underlying asset at any time, with one caveat. There must be enough liquidity in the pool. If borrowers have taken out 95% of the pool's assets, a supplier who wants to withdraw 10% of the pool cannot, until either some of those borrowers repay or new supply arrives. This constraint is the central piece of the interest rate model.
The borrow side
A borrower deposits collateral, then takes out a loan against it. The collateral is typically a different asset from the loan, for example deposit ETH as collateral and borrow USDC against it. Posting collateral and borrowing happen in the same contract, and the protocol tracks each user's collateral and debt positions in a single account.
The amount that can be borrowed is bounded by the Loan-to-Value ratio, abbreviated LTV. For ETH on Aave, the LTV might be 80%. With 1 ETH deposited as collateral while ETH trades at $3,000, a borrower can take out up to $2,400 worth of USDC.
The borrow position pays interest. On the supply side, interest accrues by holding an appreciating receipt token. On the borrow side, interest compounds the debt itself. A borrower who took out 2,400 USDC today owes 2,400 USDC plus accrued interest at any later time. The interest rate is set by the same mechanism that determines supplier yield.
A position has a health factor that measures distance from liquidation:
health = (collateral_value × liquidation_threshold) / debt_valueWhen health >= 1, the position is safe. When health < 1, the position is liquidatable. Anyone can step in to repay part of the borrower's debt in exchange for a share of the collateral at a discount.
The liquidation threshold sits slightly above the LTV. For ETH, the LTV might be 80% and the liquidation threshold 85%. That gap is the safety buffer. A user who borrows the maximum allowed cannot be liquidated immediately. The collateral value would have to fall by about 6% before they hit the liquidation threshold.
The interest rate curve
The pool's interest rate is a function of utilization. Utilization is the fraction of the pool currently borrowed:
U = totalBorrows / totalSupplyAt U=0, no one is borrowing. The pool is fully liquid. Suppliers can withdraw anything they want. Rates are low because there is no demand for borrows.
At U=1, everyone has borrowed. No supplier can withdraw. Rates spike because the protocol needs to either attract more supply or push some borrowers to repay.
The rate model captures this. The dominant model since Compound v1 is the kink model. From U=0 up to a kink point typically at U=80%, the rate grows linearly from a low base rate to an optimal rate of around 4%. From U=80% up to U=100%, the rate grows much more steeply, hitting 100% or higher at full utilization.
The kink point is where the model considers utilization healthy. Below the kink, rates are reasonable. Above the kink, the model penalizes borrowing aggressively and rewards new supply. The pool wants to stay below the kink. The steep curve above the kink is the protocol pushing participants to do something about the imbalance.
The supply rate falls out of the borrow rate. It equals the borrow rate times the utilization times one minus the reserve factor:
supplyRate = borrowRate × utilization × (1 − reserveFactor)The reserve factor, typically between 10% and 25%, is the protocol's cut. It builds a treasury that can cover bad debt or pay out to governance.
So if borrowRate is 5%, utilization is 80%, and reserveFactor is 10%, the supply rate is:
5% × 0.80 × 0.9 = 3.6%Suppliers always earn less than borrowers pay. The spread is utilization-dependent and absorbs the reserve factor.
Liquidations
When a position's health factor drops below 1, anyone can liquidate it. The liquidate function takes the borrower's address and an amount of debt to repay. The liquidator transfers that amount of the debt asset into the protocol, and the protocol transfers a corresponding amount of the borrower's collateral, at a discounted price, to the liquidator.
The discount is the liquidation bonus. For ETH on Aave it sits around 5% to 10%, depending on the asset's volatility. For more volatile assets, the bonus is higher. The size of the bonus reflects the risk the liquidator takes by holding the seized collateral until they can sell it on the open market.
Most protocols cap each individual liquidation at 50% of the position's debt. This is the close factor. A single liquidation never fully wipes out a borrower's position, only the part needed to push the health factor back above 1. If the price keeps falling after one liquidation, another liquidator can step in for the next 50%, and so on.
The oracle dependency
Every collateral and debt asset needs a price oracle. The protocol reads oracle prices to compute collateral value when a borrower opens a position, to compute the health factor of every open position, and to determine how much collateral the liquidator receives for a given amount of debt repaid.
If the oracle is wrong, the protocol is wrong. Two specific failure modes follow from this.
Oracle reports a price too high. Borrowers take out more debt than their collateral is worth. When the price corrects downward, those positions are underwater, with debt larger than collateral. Liquidators will not touch them, because the math no longer works out. The collateral they would receive is worth less than the debt they would have to repay. The protocol absorbs the loss as bad debt.
Oracle reports a price too low. Healthy positions are marked as liquidatable. Liquidators rush in and seize collateral from positions that, by true market prices, should never have been liquidated. The borrower loses collateral they should not have lost.
This is the central reason every production lending protocol uses Chainlink price feeds rather than reading prices off a single AMM pool. A single AMM pool's spot price can be moved within one transaction by a sufficiently large swap or flash loan. The flash loans lecture covered the attack walkthrough end to end. Chainlink aggregates many off-chain price sources and updates only after the aggregated price moves beyond a threshold, making single-transaction manipulation infeasible.
Some protocols use multiple oracles for the same asset and cross-check. If two sources diverge by more than a threshold, the protocol pauses operations on that asset until the divergence resolves. This costs availability but prevents bad debt during oracle outages.
How lending designs differ
The "pool per asset" design described above is the original Compound V2 / Aave V2 shape. Several variants have emerged.
Isolated lending markets. Compound V3 and Morpho Blue are the main examples. Instead of one global pool per asset, each lending market is a separate contract pairing one collateral asset with one borrow asset. ETH/USDC, wBTC/USDC, stETH/ETH, each its own market. Asset failure stays contained. A bug or oracle problem in one market does not affect others. The tradeoff is fragmented liquidity, since suppliers must choose specific markets rather than the protocol allocating their deposit across all opportunities.
Curated vaults built on top of isolated markets. Morpho Vaults, MetaMorpho, and Yearn V3 are examples. Suppliers deposit into a vault, and a curator allocates the vault's deposits across multiple isolated markets, rebalancing as rates change and pulling out of markets that show signs of risk. The supplier gets diversification and active management without managing positions directly. The curator can be an EOA, a multisig, or a smart contract executing rules.
Cross-collateral pools with finer-grained controls. Aave V3, Spark, and Radiant are examples. The original Compound model evolved. Each asset has its own LTV and liquidation threshold, plus per-asset borrow caps and supply caps. Isolation mode lets the protocol mark certain risky assets as collateral that can only back stablecoin borrows, never other volatile assets. This contains tail risk without splitting into separate markets.
Each design optimizes for a different tradeoff. Pooled designs are simplest and most capital-efficient. Isolated designs are safest against asset-specific failures. Curated vaults split the difference, giving suppliers a familiar deposit-once experience while operating on top of safer underlying markets.
What can go wrong in practice
Five failure modes worth knowing.
Oracle manipulation. Covered above. The defense is well-designed oracles, primarily Chainlink with staleness checks and multi-source aggregation.
Liquidation cascades. In stressed markets, a price drop liquidates the first set of positions. Liquidators seize collateral and sell it on the open market to lock in their profit. Those sales push the price further down. More positions become liquidatable. The cycle continues until the market stabilizes or the protocol runs out of liquidatable collateral. This pattern hit several lending protocols during the May 2022 Terra/Luna collapse, the March 2023 USDC depeg, and the August 2024 yen carry trade unwinds.
Interest rate spikes leaving suppliers stuck. When utilization hits 100%, the rate goes through the roof, but there is no liquidity to withdraw. Suppliers earn high paper yields they cannot realize, until borrowers repay or new supply arrives. During DeFi panic moments, large suppliers withdrawing at once can drive a healthy pool to 100% utilization within minutes.
Bad debt. Positions whose collateral has fallen below the debt value. No liquidator will touch them, since the math is upside down. The protocol either eats the loss, drawing from its reserve, or socializes it across suppliers, where everyone takes a haircut. Major bad-debt events have hit Mango Markets in October 2022, Inverse Finance in April 2022, and Euler V1 in March 2023.
Smart contract bugs. Lending protocols hold huge balances and have complex math. Both have produced exploits. Cream Finance, Euler V1, and others have lost nine-figure sums to bugs in liquidation logic, accounting math, or flash-loan-mediated reentry.
Bridge or wrapped-asset risk. Many lending markets accept wrapped versions of cross-chain assets, like wBTC on Ethereum or USDC.e on L2s. The wrapper is only as safe as the bridge. If the bridge gets exploited, the wrapped asset depegs from the underlying, and any lending market holding it as collateral has bad debt overnight.
Reading a real lending position
To make the mechanics concrete, here is what a borrower's state looks like at any moment in a pooled lending protocol. The state is spread across several mappings.
// Solidity 0.8.20, Ethereum mainnet
// Conceptual layout. Real code uses packed structs and indexed math.
mapping(address user => mapping(address asset => uint256)) public collateralBalance;
mapping(address user => mapping(address asset => uint256)) public debtBalance;
mapping(address asset => uint256) public assetLTV; // 0-10000 (bps)
mapping(address asset => uint256) public assetLiqThreshold; // 0-10000 (bps)The health factor view function aggregates the above:
function getHealthFactor(address user) external view returns (uint256) {
uint256 weightedCollateral;
uint256 totalDebt;
for (uint256 i = 0; i < userCollateralAssets[user].length; i++) {
address asset = userCollateralAssets[user][i];
uint256 balance = collateralBalance[user][asset];
uint256 price = oracle.getPrice(asset);
weightedCollateral += balance * price * assetLiqThreshold[asset] / 10000;
}
for (uint256 i = 0; i < userDebtAssets[user].length; i++) {
address asset = userDebtAssets[user][i];
uint256 balance = debtBalance[user][asset];
uint256 price = oracle.getPrice(asset);
totalDebt += balance * price;
}
if (totalDebt == 0) return type(uint256).max;
return (weightedCollateral * 1e18) / totalDebt;
}Three things to notice. The function loops over the user's collateral and debt assets. The loop is bounded by the number of distinct assets the user has positions in, which is typically small, one or two per side. The loop is fine.
The oracle gets called once per asset, every time anyone checks a health factor. The oracle's read cost matters, both for liquidation checks and for borrow-time LTV checks.
The health factor formula uses the liquidation threshold for each collateral asset. The LTV controls how much can be borrowed at the moment a position opens. The liquidation threshold controls when liquidation triggers. The two are different numbers on purpose. The gap between them is the buffer.
Production code is more complex than the above. Real Aave V3 caches indexed rate math, packs storage for gas, and handles bridged assets specially. The conceptual shape stays the same.