Skip to main content

Callbacks

Overview

In the context of Morpho (formerly known as Morpho Blue), callbacks are mechanisms that enable developers to perform arbitrary actions in the intermediate stage of a transaction process (before the ERC20 token transfer). Those callbacks are present in the following entry points: supply, supplyCollateral, repay and liquidate. Using them avoids having to deal with flash loans that are limited by liquidity and that perform sub-optimal back and forth token transfers. Thus, endless possibilities are unlocked and it costs less gas for integrators.

Use case

Typical use cases using callbacks are:

  • (De)Leverage one's position;
  • Liquidate a position without flashloans;
  • Migrate from one market to another.

Visual

Below is a visual comparison between leveraging a position by hand VS using external flashloan VS using a callback:

Callbacks

Examples

To illustrate the use of callbacks, refer to the:

Morpho Snippets Repository

This repository contains examples including:

leverageMe

Create a leveraged position with a given leverageFactor on the marketParams market of Morpho for the sender.

deleverageMe

Deleverages the sender on the given marketParams market of Morpho by repaying his debt and withdrawing his collateral. The withdrawn assets are sent to the sender.

Logic

The leverageMe() function exemplifies leveraging a position, taking the following inputs:

  • leverageFactor: The factor by which to leverage.
  • initAmountCollateral: Initial amount of collateral (already owned by the sender before the transaction).
  • marketParams: The market parameters.

The flow is the following:

  1. Initialization: The function starts by initializing variables and calculating necessary values.
  2. Calling Morpho Singleton: It then calls the Morpho singleton with the callback data.
  3. Executing Callback: The callback data triggers the onMorphoSupplyCollateral function. This function:
    1. Borrows the loan amount of loan asset via Morpho,
    2. Swaps the borrowed liquidity into the collateral token using ISwap.

Leverage Mechanics: On the Morpho contract, the logic transfers the user's leverage position up to the amount of initAmountCollateral * leverageFactor

Swap

  1. ISwap interface: The ISwap interface represents an arbitrary DEX or aggregator, such as 1Inch or Uniswap.
  2. Adapting Swap Function: Function swapLoanToCollat should align with the swap contract's functions.
  3. Consideration for Fees and Slippage: Be mindful of fees and slippage in the swap process.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/* IMPORTS */

import {IMorphoSupplyCollateralCallback} from "@morpho-blue/interfaces/IMorphoCallbacks.sol";
import {Id, IMorpho, MarketParams} from "@morpho-blue/interfaces/IMorpho.sol";
import {MorphoLib} from "@morpho-blue/libraries/periphery/MorphoLib.sol";
import {MarketParamsLib} from "@morpho-blue/libraries/MarketParamsLib.sol";

import {SafeTransferLib, ERC20} from "@solmate/utils/SafeTransferLib.sol";

/* INTERFACES */

interface ISwap {
/// @notice Swaps loan token to collateral token.
/// @param amount The amount of loan token to swap.
/// @return returnedAmount The amount of collateral token returned.
function swapLoanToCollat(uint256 amount) external returns (uint256 returnedAmount);
}

/* CONTRACT */

contract CallbacksSnippets is IMorphoSupplyCollateralCallback {
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 = IMorpho _morpho;
swapper = _swapper;
}

modifier onlyMorpho() {
require(msg.sender == address(morpho), "msg.sender should be Morpho");
_;
}

/// Type of collateral supply callback data.
struct SupplyCollateralData {
uint256 loanAmount;
MarketParams marketParams;
address user;
}

/* FUNCTIONS */

function onMorphoSupplyCollateral(uint256 amount, bytes calldata data) external onlyMorpho {
SupplyCollateralData memory decoded = abi.decode(data, (SupplyCollateralData));
(uint256 amountBis,) = morpho.borrow(decoded.marketParams, decoded.loanAmount, 0, decoded.user, address(this));

ERC20(decoded.marketParams.loanToken).approve(address(swapper), amount);

// Logic to Implement. The following example is a swap, but it could be something else such as a 'unwrap + stake + wrap staked' for
// wETH(wstETH) Market.
swapper.swapLoanToCollat(amountBis);
}

/// @notice Creates a leveraged position with a specified `leverageFactor` on the `marketParams` market of Morpho
/// for the sender.
/// @dev Requires the sender to hold `initAmountCollateral` and approve this contract to manage their positions on
/// Morpho (as this contract will borrow on behalf of the sender).
/// @param leverageFactor The desired leverage factor, cannot exceed the limit of 1/1-LLTV.
/// @param initAmountCollateral The initial amount of collateral held by the sender.
/// @param marketParams Parameters of the market on which to execute the leverage operation.
function leverageMe(uint256 leverageFactor, uint256 initAmountCollateral, MarketParams calldata marketParams)
public
{
// Transfer the initial collateral from the sender to this contract.
ERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), initAmountCollateral);

// Calculate the final amount of collateral based on the leverage factor.
uint256 finalAmountCollateral = initAmountCollateral * leverageFactor;

// Calculate the amount of LoanToken to be borrowed and swapped against CollateralToken.
// Note: In this simplified example, the price is assumed to be `ORACLE_PRICE_SCALE`.
// In a real-world scenario:
// - The price might not equal `ORACLE_PRICE_SCALE`, and the oracle's price should be factored into the
// calculation, like this:
// (leverageFactor - 1) * initAmountCollateral.mulDivDown(ORACLE_PRICE_SCALE, IOracle(oracle).price())
// - Consideration for fees and slippage is crucial to accurately compute `loanAmount`.
uint256 loanAmount = (leverageFactor - 1) * initAmountCollateral;

// Approve the maximum amount to Morpho on behalf of the collateral token.
_approveMaxTo(marketParams.collateralToken, address(morpho));

// Supply the collateral to Morpho, initiating the leverage operation.
morpho.supplyCollateral(
marketParams,
finalAmountCollateral,
msg.sender,
abi.encode(SupplyCollateralData(loanAmount, marketParams, msg.sender))
);
}

function _approveMaxTo(address asset, address spender) internal {
if (ERC20(asset).allowance(address(this), spender) == 0) {
ERC20(asset).safeApprove(spender, type(uint256).max);
}
}
}