Skip to main content

Understanding and Tracking Positions in Morpho

  1. This guide introduces you to the essential functionalities within Morpho that allow users and developers to track and understand their positions, offering insights into the mechanics of supply and borrow Annual Percentage Yield (APY), user assets, market totals, and health factors.
  2. Do not forget to have a look at the MorphoBalancesLib which is a library that allows you to accrue interest and update the assets values of a market. As those contracts are not deployed by the Morpho Association, you will find the offchain computation to do so in the TypeScript section below.

Why Tracking Positions?

Tracking financial positions in Morpho enables users to:

  • Optimize Returns: Understanding supply and borrow APY helps users make informed decisions to maximize their investment returns.
  • Market Exposure: By monitoring collateral and borrow balances, users can effectively manage their exposure to specific markets, ensuring they maintain healthy positions.
  • Strategic Decisions: Access to detailed market data allows users to strategize their next moves based on the overall market supply and borrow status.

Importance of Accruing Interests

The scripts provided herein are designed to guide users on the proper logic for fetching onchain data from Morpho. It is important to highlight the significance of accruing interest when building a system that tracks positions. Accruing interest ensures that the data retrieved is both current and accurate, considering that interest is only accrued up to the last transaction. This necessity is addressed by the MorphoBalancesLib library in the Solidity examples and by the accrueInterests function implementation in the TypeScript examples.

Features Overview

This section delves into the specific functionalities provided by Morpho to track positions:

supplyAPY and borrowAPY

Calculate the supply and borrow APY for a given market, excluding potential rewards.

supplyAssetsUser, collateralAssetsUser & borrowAssetsUser

Determine the total supply, collateral, and borrow balances of a specific user in a market. These figures are critical for understanding one's standing and exposure in the market.

marketTotalSupply & marketTotalBorrow

Reveal the total supply and borrow volumes in a specific market, providing a snapshot of the market's liquidity and borrowing activity.

userHealthFactor

Assess the health factor of a user's position in a specific market, a crucial measure to avoid liquidation and monitor one's position.

Requirement

To deepen your understanding and explore more intricate examples, it is highly recommended to consult the README file of the Morpho Snippets. This resource is designed to complement the snippets provided here by offering detailed explanations and additional context for each example.

pragma solidity ^0.8.0;

import { IIrm } from "@morpho-blue/interfaces/IIrm.sol";
import {
Id,
IMorpho,
MarketParams,
Market,
} from "@morpho-blue/interfaces/IMorpho.sol";
import { MathLib } from "@morpho-blue/libraries/MathLib.sol";
import { MorphoBalancesLib } from "@morpho-blue/libraries/periphery/MorphoBalancesLib.sol";
import { MorphoStorageLib } from "@morpho-blue/libraries/periphery/MorphoStorageLib.sol";

/// @title Morpho Blue Snippets
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @notice The Morpho Blue Snippets contract.
contract BlueSnippets {
using MathLib for uint256;
using MorphoBalancesLib for IMorpho;

/* IMMUTABLES */

IMorpho public immutable morpho;

/* CONSTRUCTOR */

/// @notice Constructs the contract.
/// @param morphoAddress The address of the Morpho Blue contract.
constructor(address morphoAddress) {
morpho = IMorpho(morphoAddress);
}

/* VIEW FUNCTIONS */


// INFORMATIONAL: No 'Total Supply' and no 'Total Borrow' functions to calculate on chain as there could be some
// weird oracles/markets created

/// @notice Calculates the supply APY (Annual Percentage Yield) for a given market.
/// @param marketParams The parameters of the market.
/// @param market The market for which the supply APY is being calculated.
/// @return supplyApy The calculated supply APY (scaled by WAD).
function supplyAPY(MarketParams memory marketParams, Market memory market)
public
view
returns (uint256 supplyApy)
{
(uint256 totalSupplyAssets,, uint256 totalBorrowAssets,) = morpho.expectedMarketBalances(marketParams);

if (marketParams.irm != address(0)) {
uint256 utilization = totalBorrowAssets == 0 ? 0 : totalBorrowAssets.wDivUp(totalSupplyAssets);
supplyApy = borrowAPY(marketParams, market).wMulDown(1 ether - market.fee).wMulDown(utilization);
}
}

/// @notice Calculates the borrow APY (Annual Percentage Yield) for a given market.
/// @param marketParams The parameters of the market.
/// @param market The state of the market.
/// @return borrowApy The calculated borrow APY (scaled by WAD).
function borrowAPY(MarketParams memory marketParams, Market memory market)
public
view
returns (uint256 borrowApy)
{
if (marketParams.irm != address(0)) {
borrowApy = IIrm(marketParams.irm).borrowRateView(marketParams, market).wTaylorCompounded(365 days);
}
}

/// @notice Calculates the total supply balance of a given user in a specific market.
/// @param marketParams The parameters of the market.
/// @param user The address of the user whose supply balance is being calculated.
/// @return totalSupplyAssets The calculated total supply balance.
function supplyAssetsUser(MarketParams memory marketParams, address user)
public
view
returns (uint256 totalSupplyAssets)
{
totalSupplyAssets = morpho.expectedSupplyAssets(marketParams, user);
}

/// @notice Calculates the total collateral balance of a given user in a specific market.
/// @dev It uses extSloads to load only one storage slot of the Position struct and save gas.
/// @param marketId The identifier of the market.
/// @param user The address of the user whose collateral balance is being calculated.
/// @return totalCollateralAssets The calculated total collateral balance.
function collateralAssetsUser(Id marketId, address user) public view returns (uint256 totalCollateralAssets) {
bytes32[] memory slots = new bytes32[](1);
slots[0] = MorphoStorageLib.positionBorrowSharesAndCollateralSlot(marketId, user);
bytes32[] memory values = morpho.extSloads(slots);
totalCollateralAssets = uint256(values[0] >> 128);
}

/// @notice Calculates the total borrow balance of a given user in a specific market.
/// @param marketParams The parameters of the market.
/// @param user The address of the user whose borrow balance is being calculated.
/// @return totalBorrowAssets The calculated total borrow balance.
function borrowAssetsUser(MarketParams memory marketParams, address user)
public
view
returns (uint256 totalBorrowAssets)
{
totalBorrowAssets = morpho.expectedBorrowAssets(marketParams, user);
}

/// @notice Calculates the total supply of assets in a specific market.
/// @param marketParams The parameters of the market.
/// @return totalSupplyAssets The calculated total supply of assets.
function marketTotalSupply(MarketParams memory marketParams) public view returns (uint256 totalSupplyAssets) {
totalSupplyAssets = morpho.expectedTotalSupplyAssets(marketParams);
}

/// @notice Calculates the total borrow of assets in a specific market.
/// @param marketParams The parameters of the market.
/// @return totalBorrowAssets The calculated total borrow of assets.
function marketTotalBorrow(MarketParams memory marketParams) public view returns (uint256 totalBorrowAssets) {
totalBorrowAssets = morpho.expectedTotalBorrowAssets(marketParams);
}

/// @notice Calculates the health factor of a user in a specific market.
/// @param marketParams The parameters of the market.
/// @param id The identifier of the market.
/// @param user The address of the user whose health factor is being calculated.
/// @return healthFactor The calculated health factor.
function userHealthFactor(MarketParams memory marketParams, Id id, address user)
public
view
returns (uint256 healthFactor)
{
uint256 collateralPrice = IOracle(marketParams.oracle).price();
uint256 collateral = morpho.collateral(id, user);
uint256 borrowed = morpho.expectedBorrowAssets(marketParams, user);

uint256 maxBorrow = collateral.mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(marketParams.lltv);

if (borrowed == 0) return type(uint256).max;
healthFactor = maxBorrow.wDivDown(borrowed);
}
}