Track Borrow Positions

Learn how to track any position borrowed through Morpho, on-chain via a Smart Contract & off-chain via ethers.js.

  • For Morpho-Aave-V2 and Morpho-Compound, there are lenses deployed.

Morpho's Lens exposes generic information about the protocol, such as the total borrows in USD (18 decimals). Anyone can query it off-chain through Etherscan or with ethers.js, or on-chain using a Smart Contract.

In addition to querying generic data, anyone can, at anytime, query Morpho's Lens to get information about anyone's borrow position. Here is the repository:

  • For Morpho-Aave-V3, there is no lens but instead, there are snippets provided. The github repository is here:

👇 Here are concrete examples 👇

Morpho-Aave Instances

  • Morpho-Aave-V3 & Morpho-Aave-V2 are Deployed.

The structure of the following solidity snippets is as follows:

  1. Imports

  2. Contracts

/// IMPORTS ///

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

import {IPool, IPoolAddressesProvider} from "@aave-v3-core/interfaces/IPool.sol";
import {IAaveOracle} from "@aave-v3-core/interfaces/IAaveOracle.sol";
import {IMorpho} from "@morpho-aave-v3/interfaces/IMorpho.sol";

import {ERC20} from "@solmate/tokens/ERC20.sol";
import {Types} from "@morpho-aave-v3/libraries/Types.sol";
import {MarketLib} from "@snippets/morpho-aave-v3/libraries/MarketLib.sol";
import {Utils} from "@snippets/morpho-aave-v3/Utils.sol";
import {Math} from "@morpho-utils/math/Math.sol";
import {WadRayMath} from "@morpho-utils/math/WadRayMath.sol";
import {DataTypes} from "@aave-v3-core/protocol/libraries/types/DataTypes.sol";
import {ReserveConfiguration} from "@aave-v3-core/protocol/libraries/configuration/ReserveConfiguration.sol";

/// FUNCTIONS ///

/// @title Snippets
/// @author Morpho Labs
/// @custom:contact security@morpho.xyz
/// @notice Code snippets for Morpho-Aave V3.
contract Snippets {
    using Math for uint256;
    using WadRayMath for uint256;
    using MarketLib for Types.Market;
    using ReserveConfiguration for DataTypes.ReserveConfigurationMap;

    IMorpho public immutable morpho;
    IPoolAddressesProvider public immutable addressesProvider;
    IPool public immutable pool;
    uint8 public immutable eModeCategoryId;

    constructor(address morphoAddress) {
        morpho = IMorpho(morphoAddress);
        pool = IPool(morpho.pool());
        addressesProvider = IPoolAddressesProvider(morpho.addressesProvider());
        eModeCategoryId = uint8(morpho.eModeCategoryId());
    }
    

    /// @notice Computes and returns the total distribution of borrows through Morpho, using virtually updated indexes.
    /// @return p2pBorrowAmount The total borrowed amount matched peer-to-peer, subtracting the borrow delta (in base currency).
    /// @return poolBorrowAmount The total borrowed amount on the underlying pool, adding the borrow delta (in base currency).
    /// @return totalBorrowAmount The total amount borrowed through Morpho (in base currency).
    function totalBorrow()
        public
        view
        returns (uint256 p2pBorrowAmount, uint256 poolBorrowAmount, uint256 totalBorrowAmount)
    {
        address[] memory marketAddresses = morpho.marketsCreated();

        uint256 underlyingPrice;
        uint256 nbMarkets = marketAddresses.length;

        for (uint256 i; i < nbMarkets; ++i) {
            address underlying = marketAddresses[i];

            DataTypes.ReserveConfigurationMap memory reserve = pool.getConfiguration(underlying);
            underlyingPrice = assetPrice(underlying, reserve.getEModeCategory());
            uint256 assetUnit = 10 ** reserve.getDecimals();

            (uint256 marketP2PBorrowAmount, uint256 marketPoolBorrowAmount) = marketBorrow(underlying);

            p2pBorrowAmount += (marketP2PBorrowAmount * underlyingPrice) / assetUnit;
            poolBorrowAmount += (marketPoolBorrowAmount * underlyingPrice) / assetUnit;
        }

        totalBorrowAmount = p2pBorrowAmount + poolBorrowAmount;
    }


    /// @notice Returns the borrow rate per year a given user is currently experiencing on a given market.
    /// @param underlying The address of the underlying asset.
    /// @param user The user to compute the borrow rate per year for.
    /// @return borrowRatePerYear The borrow rate per year the user is currently experiencing (in ray).
    function borrowAPR(address underlying, address user) public view returns (uint256 borrowRatePerYear) {
        (uint256 balanceInP2P, uint256 balanceOnPool,) = borrowBalance(underlying, user);
        (uint256 poolSupplyRate, uint256 poolBorrowRate) = poolAPR(underlying);

        Types.Market memory market = morpho.market(underlying);
        Types.Indexes256 memory indexes = morpho.updatedIndexes(underlying);

        uint256 p2pBorrowRate = Utils.p2pBorrowAPR(
            Utils.P2PRateComputeParams({
                poolSupplyRatePerYear: poolSupplyRate,
                poolBorrowRatePerYear: poolBorrowRate,
                poolIndex: indexes.borrow.poolIndex,
                p2pIndex: indexes.borrow.p2pIndex,
                proportionIdle: 0,
                p2pDelta: market.deltas.borrow.scaledDelta,
                p2pTotal: market.deltas.borrow.scaledP2PTotal,
                p2pIndexCursor: market.p2pIndexCursor,
                reserveFactor: market.reserveFactor
            })
        );

        borrowRatePerYear = Utils.weightedRate(p2pBorrowRate, poolBorrowRate, balanceInP2P, balanceOnPool);
    }

    /// @notice Computes and returns the current borrow rate per year experienced on average on a given market.
    /// @param underlying The address of the underlying asset.
    /// @return avgBorrowRatePerYear The market's average borrow rate per year (in ray).
    /// @return p2pBorrowRatePerYear The market's p2p borrow rate per year (in ray).
    ///@return poolBorrowRatePerYear The market's pool borrow rate per year (in ray).
    function avgBorrowAPR(address underlying)
        public
        view
        returns (uint256 avgBorrowRatePerYear, uint256 p2pBorrowRatePerYear, uint256 poolBorrowRatePerYear)
    {
        Types.Market memory market = morpho.market(underlying);
        Types.Indexes256 memory indexes = morpho.updatedIndexes(underlying);

        uint256 poolSupplyRatePerYear;
        (poolSupplyRatePerYear, poolBorrowRatePerYear) = poolAPR(underlying);

        p2pBorrowRatePerYear = Utils.p2pBorrowAPR(
            Utils.P2PRateComputeParams({
                poolSupplyRatePerYear: poolSupplyRatePerYear,
                poolBorrowRatePerYear: poolBorrowRatePerYear,
                poolIndex: indexes.borrow.poolIndex,
                p2pIndex: indexes.borrow.p2pIndex,
                proportionIdle: 0,
                p2pDelta: 0, // Simpler to account for the delta in the weighted avg.
                p2pTotal: 0,
                p2pIndexCursor: market.p2pIndexCursor,
                reserveFactor: market.reserveFactor
            })
        );

        avgBorrowRatePerYear = Utils.weightedRate(
            p2pBorrowRatePerYear,
            poolBorrowRatePerYear,
            market.trueP2PBorrow(indexes),
            ERC20(market.variableDebtToken).balanceOf(address(morpho))
        );
    }

    /// @notice Computes and returns the total distribution of borrows for a given market, using virtually updated indexes.
    /// @param underlying The address of the underlying asset to check.
    /// @return p2pBorrow The total borrowed amount (in underlying) matched peer-to-peer, subtracting the borrow delta.
    /// @return poolBorrow The total borrowed amount (in underlying) on the underlying pool, adding the borrow delta.
    function marketBorrow(address underlying) public view returns (uint256 p2pBorrow, uint256 poolBorrow) {
        Types.Market memory market = morpho.market(underlying);
        Types.Indexes256 memory indexes = morpho.updatedIndexes(underlying);

        p2pBorrow = market.trueP2PBorrow(indexes);
        poolBorrow = ERC20(market.variableDebtToken).balanceOf(address(morpho));
    }

    
    /// @notice Returns the borrow balance in underlying of a given user in a given market.
    /// @param underlying The address of the underlying asset.
    /// @param user The user to determine balances of.
    /// @return balanceInP2P The balance in peer-to-peer of the user (in underlying).
    /// @return balanceOnPool The balance on pool of the user (in underlying).
    /// @return totalBalance The total balance of the user (in underlying).
    function borrowBalance(address underlying, address user)
        public
        view
        returns (uint256 balanceInP2P, uint256 balanceOnPool, uint256 totalBalance)
    {
        Types.Indexes256 memory indexes = morpho.updatedIndexes(underlying);

        balanceInP2P = morpho.scaledP2PBorrowBalance(underlying, user).rayMulUp(indexes.borrow.p2pIndex);
        balanceOnPool = morpho.scaledPoolBorrowBalance(underlying, user).rayMulUp(indexes.borrow.poolIndex);
        totalBalance = balanceInP2P + balanceOnPool;
    }


    /// @dev Computes and returns the underlying pool rates for a specific market.
    /// @param underlying The underlying pool market address.
    /// @return poolSupplyRatePerYear The market's pool supply rate per year (in ray).
    /// @return poolBorrowRatePerYear The market's pool borrow rate per year (in ray).
    function poolAPR(address underlying)
        public
        view
        returns (uint256 poolSupplyRatePerYear, uint256 poolBorrowRatePerYear)
    {
        DataTypes.ReserveData memory reserve = pool.getReserveData(underlying);
        poolSupplyRatePerYear = reserve.currentLiquidityRate;
        poolBorrowRatePerYear = reserve.currentVariableBorrowRate;
    }

    /// @notice Returns the price of a given asset.
    /// @param asset The address of the asset to get the price of.
    /// @param reserveEModeCategoryId Aave's associated reserve e-mode category.
    /// @return price The current price of the asset.
    function assetPrice(address asset, uint256 reserveEModeCategoryId) public view returns (uint256 price) {
        address priceSource;
        if (eModeCategoryId != 0 && reserveEModeCategoryId == eModeCategoryId) {
            priceSource = pool.getEModeCategoryData(eModeCategoryId).priceSource;
        }

        IAaveOracle oracle = IAaveOracle(addressesProvider.getPriceOracle());

        if (priceSource != address(0)) {
            price = oracle.getAssetPrice(priceSource);
        }

        if (priceSource == address(0) || price == 0) {
            price = oracle.getAssetPrice(asset);
        }
    }
}

Morpho-Compound

// SPDX-License-Identifier: GNU AGPLv3
pragma solidity ^0.8.16;

import {ILens} from "@morpho-org/morpho-core-v1/contracts/compound/interfaces/ILens.sol";
import {IMorpho, ICompoundOracle} from "@morpho-org/morpho-core-v1/contracts/compound/interfaces/IMorpho.sol";

import {CompoundMath} from "@morpho-org/morpho-utils/src/math/CompoundMath.sol";

contract MorphoCompoundBorrower {
    using CompoundMath for uint256;

    address public constant CDAI = 0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643;
    address public constant CWBTC = 0xC11b1268C1A384e55C48c2391d8d480264A3A7F4;

    address public constant LENS = 0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67;
    address public constant MORPHO = 0x8888882f8f843896699869179fB6E4f7e3B58888;

    ICompoundOracle public immutable ORACLE;

    constructor() {
        ORACLE = ICompoundOracle(IMorpho(MORPHO).comptroller().oracle());
    }

    /// @notice Returns the distribution of WBTC borrowed by this contract through Morpho-Compound.
    /// @return borrowedOnPool The amount of WBTC borrowed on Compound's pool (with 8 decimals, the number of decimals of WBTC).
    /// @return borrowedP2P The amount of WBTC borrowed peer-to-peer through Morpho-Compound (with 8 decimals, the number of decimals of WBTC).
    function getWBTCBorrowBalance()
        public
        view
        returns (uint256 borrowedOnPool, uint256 borrowedP2P)
    {
        (borrowedOnPool, borrowedP2P, ) = ILens(LENS)
            .getCurrentBorrowBalanceInOf(
                CWBTC2, // the WBTC market, represented by the cWBTC2 ERC20 token
                address(this) // the address of the user you want to know the borrow of
            );
    }

    /// @notice Returns the distribution of WBTC borrowed by this contract through Morpho-Compound.
    /// @return borrowedOnPoolUSD The USD value of the amount of WBTC borrowed on Compound's pool (with 18 decimals, whatever the market).
    /// @return borrowedP2PUSD The USD value of the amount of WBTC borrowed peer-to-peer through Morpho-Compound (with 18 decimals, whatever the market).
    function getWBTCBorrowBalanceUSD()
        public
        view
        returns (uint256 borrowedOnPoolUSD, uint256 borrowedP2PUSD)
    {
        (uint256 borrowedOnPool, uint256 borrowedP2P) = getWBTCBorrowBalance();

        uint256 oraclePrice = ORACLE.getUnderlyingPrice(CWBTC2); // with (36 - nb decimals of WBTC = 30) decimals

        borrowedOnPoolUSD = borrowedOnPool.mul(oraclePrice); // with 18 decimals, whatever the underlying token
        borrowedP2PUSD = borrowedP2P.mul(oraclePrice); // with 18 decimals, whatever the underlying token
    }

    /// @notice Returns the average borrow rate per block experienced on the DAI market.
    /// @dev The borrow rate experienced on a market is specific to each user,
    ///      dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
    /// @return The rate per block at which borrow interests are accumulated on average on the DAI market (with 18 decimals, whatever the market).
    function getDAIAvgBorrowRatePerBlock() public view returns (uint256) {
        return
            ILens(LENS).getAverageBorrowRatePerBlock(
                CDAI // the DAI market, represented by the cDAI ERC20 token
            );
    }

    /// @notice Returns the average borrow APR experienced on the DAI market.
    /// @dev The borrow rate experienced on a market is specific to each user,
    ///      dependending on how their borrow is matched peer-to-peer or supplied to the Compound pool.
    /// @return The APR at which borrow interests are accumulated on average on the DAI market (with 18 decimals, whatever the market).
    function getDAIAvgBorrowAPR() public view returns (uint256) {
        return getDAIAvgBorrowRatePerBlock() * BLOCKS_PER_YEAR;
    }

    /// @notice Returns the borrow rate per block this contract experiences on the WBTC market.
    /// @dev The borrow rate experienced on a market is specific to each user,
    ///      dependending on how their borrow is matched peer-to-peer or supplied to the Compound pool.
    /// @return The rate per block at which borrow interests are accrued by this contract on the WBTC market (with 18 decimals).
    function getWBTCBorrowRatePerBlock() public view returns (uint256) {
        return
            ILens(LENS).getCurrentUserBorrowRatePerBlock(
                CWBTC2, // the WBTC market, represented by the cWBTC2 ERC20 token
                address(this) // the address of the user you want to know the borrow rate of
            );
    }

    /// @notice Returns the expected APR at which borrow interests are accrued by this contract, on the WBTC market.
    /// @return The APR at which WBTC borrow interests are accrued (with 18 decimals, whatever the market).
    function getWBTCBorrowAPR() public view returns (uint256) {
        uint256 borrowRatePerBlock = getWBTCBorrowRatePerBlock();

        return borrowRatePerBlock * BLOCKS_PER_YEAR;
    }

    /// @notice Returns the borrow APR this contract will experience (at maximum) if it borrows the given amount from the WBTC market.
    /// @dev The borrow rate experienced on a market is specific to each user,
    ///      dependending on how their borrow is matched peer-to-peer or supplied to the Compound pool.
    /// @return The APR at which borrow interests are accrued by this contract on the WBTC market (with 18 decimals).
    function getWBTCNextSupplyAPR(uint256 _amount)
        public
        view
        returns (uint256)
    {
        return
            ILens(LENS).getNextUserSupplyRatePerBlock(
                CWBTC2, // the WBTC market, represented by the cWBTC2 ERC20 token
                address(this), // the address of the user you want to know the next supply rate of
                _amount
            ) * BLOCKS_PER_YEAR;
    }

    /// @notice Returns the expected amount of borrow interests accrued by this contract, on the WBTC market, after `_nbBlocks`.
    /// @return The expected amount of WBTC borrow interests accrued (in 8 decimals, the number of decimals of WBTC).
    function getWBTCExpectedAccruedInterests(uint256 _nbBlocks)
        public
        view
        returns (uint256)
    {
        (uint256 borrowedOnPool, uint256 borrowedP2P) = getWBTCBorrowBalance();
        uint256 borrowRatePerBlock = getWBTCBorrowRatePerBlock();

        return
            (borrowedOnPool + borrowedP2P).mul(borrowRatePerBlock) * _nbBlocks;
    }

    /// @notice Returns whether this contract is near liquidation (with a 5% threshold) on the WBTC market.
    /// @dev The markets borrowed (in this example, WBTC only) need to be virtually updated to compute the correct health factor.
    function isApproxLiquidatable() public view returns (bool) {
        return
            ILens(LENS).getUserHealthFactor(
                address(this), // the address of the user you want to know the health factor of
                ILens(LENS).getEnteredMarkets(address(this)) // the markets entered by the user, to make sure the health factor accounts for interests accrued
            ) <= 1.05e18;
    }
}