Morpho Liquidation
Overview
As stated in the liquidation section: the liquidation process in Morpho can be triggered when an account becomes unhealthy, which occurs when its Loan-to-Value (LTV) exceeds the Liquidation Loan-to-Value (LLTV) of a market. In this scenario, liquidators can liquidate up to 100% of the account's debt and receive a corresponding value in collateral, along with a Liquidation Incentive (LI).
Unlike most lending platforms, Morpho has a unique mechanism to realize bad debt when it is incurred. In cases where liquidation leaves an account with remaining debt and no collateral, the loss is proportionally distributed among all lenders, keeping the market fully operational.
Use Case
Liquidation in Morpho serves several critical functions:
- Risk Management: It helps manage the risk of default by enabling the recovery of assets from unhealthy accounts.
- Market Stability: Addressing bad debt effectively ensures the market's healthiness in the long run.
- Incentive for Liquidators: The LI incentivizes liquidators to participate in the liquidation process, thereby maintaining the health and liquidity of the market.
Creating a bot
There are some liquidation bots created by contributors in the community, you can find it in the community section.
Examples
A naive example of a liquidation contract is displayed below, using solidity and fully available in the Morpho Snippets Repository
Swap
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/* IMPORTS */
import {IMorphoLiquidateCallback} from "@morpho-blue/interfaces/IMorphoCallbacks.sol";
import {Id, IMorpho, MarketParams} from "@morpho-blue/interfaces/IMorpho.sol";
import {SafeTransferLib, ERC20} from "@solmate/utils/SafeTransferLib.sol";
import {MorphoLib} from "@morpho-blue/libraries/periphery/MorphoLib.sol";
import {MarketParamsLib} from "@morpho-blue/libraries/MarketParamsLib.sol";
/* INTERFACES */
interface ISwap {
/// @notice Swaps collateral token to loan token.
/// @param amount The amount of collateral token to swap.
/// @return returnedAmount The amount of loan token returned.
function swapCollatToLoan(uint256 amount) external returns (uint256 returnedAmount);
}
/* CONTRACT */
contract CallbacksSnippets is IMorphoLiquidateCallback {
using MorphoLib for IMorpho;
using MarketParamsLib for MarketParams;
using SafeTransferLib for ERC20;
IMorpho public immutable morpho;
ISwap public immutable swapper;
constructor(IMorpho _morpho, ISwap _swapper) {
morpho = _morpho;
swapper = _swapper;
}
modifier onlyMorpho() {
require(msg.sender == address(morpho), "msg.sender should be Morpho Blue");
_;
}
/// Type of liquidation callback data.
struct LiquidateData {
address collateralToken;
}
/* FUNCTIONS */
function onMorphoLiquidate(uint256, bytes calldata data) external onlyMorpho {
LiquidateData memory decoded = abi.decode(data, (LiquidateData));
ERC20(decoded.collateralToken).approve(address(swapper), type(uint256).max);
swapper.swapCollatToLoan(ERC20(decoded.collateralToken).balanceOf(address(this)));
}
/// @notice Fully liquidates the borrow position of `borrower` on the given `marketParams` market of Morpho and
/// sends the profit of the liquidation to the sender.
/// @dev Thanks to callbacks, the sender doesn't need to hold any tokens to perform this operation.
/// @param marketParams The market to perform the liquidation on.
/// @param borrower The owner of the liquidable borrow position.
/// @param seizeFullCollat Pass `True` to seize all the collateral of `borrower`. Pass `False` to repay all of the
/// `borrower`'s debt.
function fullLiquidationWithoutCollat(MarketParams calldata marketParams, address borrower, bool seizeFullCollat)
public
returns (uint256 seizedAssets, uint256 repaidAssets)
{
Id id = marketParams.id();
uint256 seizedCollateral;
uint256 repaidShares;
if (seizeFullCollat) seizedCollateral = morpho.collateral(id, borrower);
else repaidShares = morpho.borrowShares(id, borrower);
_approveMaxTo(marketParams.loanToken, address(morpho));
(seizedAssets, repaidAssets) = morpho.liquidate(
marketParams,
borrower,
seizedCollateral,
repaidShares,
abi.encode(LiquidateData(marketParams.collateralToken))
);
ERC20(marketParams.loanToken).safeTransfer(msg.sender, ERC20(marketParams.loanToken).balanceOf(address(this)));
}
function _approveMaxTo(address asset, address spender) internal {
if (ERC20(asset).allowance(address(this), spender) == 0) {
ERC20(asset).safeApprove(spender, type(uint256).max);
}
}
}