Skip to main content

Track Borrow Positions

Learn how to track any position borrowed through Morpho, onchain via a smart contract & offchain via ethers.js.

  • For Morpho AaveV2 Optimizer and Morpho CompoundV2 Optimizer, 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 offchain through Etherscan or with ethers.js, or onchain 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:

GitHub Link

  • For Morpho AaveV3 Optimizer, there is no lens but instead, there are snippets provided. The github repository is here:

GitHub Link

👇 Here are concrete examples 👇

Morpho Aave Optimizer Instances​

  • Morpho AaveV3 Optimizer & Morpho AaveV2 Optimizer 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 AaveV3 Optimizer.
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 CompoundV2 Optimizer​

// 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 CompoundV2 Optimizer.
/// @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 CompoundV2 Optimizer (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 CompoundV2 Optimizer.
/// @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 CompoundV2 Optimizer (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;
}
}