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:
- For Morpho AaveV3 Optimizer, there is no lens but instead, there are snippets provided. The github repository is here:
👇 Here are concrete examples 👇
Morpho Aave Optimizer Instances​
- Morpho AaveV3 Optimizer & Morpho AaveV2 Optimizer are Deployed.
- V3: Solidity
- V3: ethers.js
- V2: Solidity
- V2: ethers.js
The structure of the following solidity snippets is as follows:
- Imports
- 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);
}
}
}
The structure of the following snippets is as follows:
- Imports
- Interfaces
- Functions
- Utils
/// IMPORTS ///
import { BigNumber, providers } from "ethers";
import { constants } from "ethers/lib/index";
import { PercentMath, WadRayMath } from "@morpho-labs/ethers-utils/lib/maths";
import { minBN, pow10 } from "@morpho-labs/ethers-utils/lib/utils";
/// INTERFACES ///
interface P2PRateComputeParams {
/** The pool supply rate per year (in ray). */
poolSupplyRatePerYear: BigNumber;
/** The pool borrow rate per year (in ray). */
poolBorrowRatePerYear: BigNumber;
/** The last stored pool index (in ray). */
poolIndex: BigNumber;
/** The last stored peer-to-peer index (in ray). */
p2pIndex: BigNumber;
/** The delta amount in pool unit. */
p2pDelta: BigNumber;
/** The total peer-to-peer amount in peer-to-peer unit. */
p2pAmount: BigNumber;
/** The index cursor of the given market (in bps). */
p2pIndexCursor: BigNumber;
/** The reserve factor of the given market (in bps). */
reserveFactor: BigNumber;
/** The proportion idle of the given market (in underlying). */
proportionIdle: BigNumber;
}
/// FUNCTIONS ///
/**
* This function retrieves the total borrow over the Morpho Aave v3
*
* @param provider A provider instance
*/
const getTotalBorrow = async (provider: providers.BaseProvider) => {
const { oracle, morphoAaveV3 } = getContracts(provider);
const markets = await morphoAaveV3.marketsCreated();
const marketsData = await Promise.all(
markets.map(async (underlying) => {
const [
{
variableDebtToken,
indexes: {
borrow: { p2pIndex, poolIndex },
},
deltas: {
borrow: { scaledDelta, scaledP2PTotal },
},
},
underlyingPrice,
] = await Promise.all([
morphoAaveV3.market(underlying),
oracle.getAssetPrice(underlying), // TODO: handle if emode
]);
const debtToken = VariableDebtToken__factory.connect(
variableDebtToken,
provider
);
const [decimals, poolBorrowAmount] = await Promise.all([
debtToken.decimals(),
debtToken.balanceOf(morphoAaveV3.address),
]);
const p2pBorrowAmount = zeroFloorSub(
WadRayMath.rayMul(scaledP2PTotal, p2pIndex),
WadRayMath.rayMul(scaledDelta, poolIndex)
);
return {
p2pBorrowAmount,
poolBorrowAmount,
underlyingPrice,
decimals,
};
})
);
const amounts = marketsData.reduce(
(acc, { p2pBorrowAmount, poolBorrowAmount, underlyingPrice, decimals }) => {
const toUsd = (amount: BigNumber) =>
amount.mul(underlyingPrice).div(pow10(decimals));
return {
p2pBorrowAmount: acc.p2pBorrowAmount.add(toUsd(p2pBorrowAmount)),
poolBorrowAmount: acc.poolBorrowAmount.add(toUsd(poolBorrowAmount)),
};
},
{
p2pBorrowAmount: constants.Zero,
poolBorrowAmount: constants.Zero,
}
);
return {
...amounts,
totalSupplyAmount: amounts.poolBorrowAmount.add(amounts.p2pBorrowAmount),
markets: marketsData,
};
};
/**
* This function gets the total borrow for one given market.
*
* @param underlying The address of the underlying token
* @param provider A provider instance
*/
const getTotalMarketBorrow = async (
underlying: string,
provider: providers.BaseProvider
) => {
const { morphoAaveV3 } = getContracts(provider);
const {
variableDebtToken,
indexes: {
borrow: { p2pIndex, poolIndex },
},
deltas: {
borrow: { scaledDelta, scaledP2PTotal },
},
} = await morphoAaveV3.market(underlying);
const aToken = VariableDebtToken__factory.connect(
variableDebtToken,
provider
);
const poolBorrowAmount = await aToken.balanceOf(morphoAaveV3.address);
const p2pBorrowAmount = zeroFloorSub(
WadRayMath.rayMul(scaledP2PTotal, p2pIndex),
WadRayMath.rayMul(scaledDelta, poolIndex)
);
return {
p2pBorrowAmount,
poolBorrowAmount,
totalBorrowAmount: p2pBorrowAmount.add(poolBorrowAmount),
};
};
/**
* This function retrieves the borrow balance of one given user in one given market.
*
* @param underlying The market to retrieve the borrowed liquidity.
* @param user The user address.
* @param provider A provider instance
*
* @returns The matched peer-to-peer amount, the pool amount and the total borrow amount.
*/
const getCurrentBorrowBalanceInOf = async (
underlying: string,
user: string,
provider: providers.BaseProvider
) => {
const { morphoAaveV3 } = getContracts(provider);
const [
{
borrow: { p2pIndex, poolIndex },
},
scaledP2PSupplyBalance,
scaledPoolSupplyBalance,
] = await Promise.all([
morphoAaveV3.updatedIndexes(underlying),
morphoAaveV3.scaledP2PBorrowBalance(underlying, user),
morphoAaveV3.scaledPoolBorrowBalance(underlying, user),
]);
const balanceInP2P = WadRayMath.rayMul(scaledP2PSupplyBalance, p2pIndex);
const balanceOnPool = WadRayMath.rayMul(scaledPoolSupplyBalance, poolIndex);
return {
balanceInP2P,
balanceOnPool,
totalBalance: balanceInP2P.add(balanceOnPool),
};
};
/**
* This function retrieves the borrow APY of a user on a given market and returns the result.
*
* @param underlying The market to retrieve the borrow APY.
* @param user The user address.
* @param provider A provider instance
*
* @returns The experienced rate and the total balance of the borrowed liquidity on this market.
*/
const getCurrentUserBorrowRatePerYear = async (
underlying: string,
user: string,
provider: providers.BaseProvider
) => {
const [{ balanceOnPool, balanceInP2P }, { p2pBorrowRate, poolBorrowRate }] =
await Promise.all([
getCurrentBorrowBalanceInOf(underlying, user, provider),
getBorrowRatesPerYear(underlying, provider),
]);
return getWeightedRate(
p2pBorrowRate,
poolBorrowRate,
balanceInP2P,
balanceOnPool
);
};
/**
* This function compute the P2P borrow rate and returns the result.
*
* @param params The parameters inheriting of the P2PRateComputeParams interface allowing the computation.
*
* @returns The p2p borrow rate per year un _RAY_ units.
*/
const getP2PBorrowRate = (params: P2PRateComputeParams) => {
let p2pBorrowRate: BigNumber;
if (params.poolSupplyRatePerYear.gt(params.poolBorrowRatePerYear)) {
p2pBorrowRate = params.poolBorrowRatePerYear;
} else {
const p2pRate = getWeightedAvg(
params.poolSupplyRatePerYear,
params.poolBorrowRatePerYear,
params.p2pIndexCursor
);
p2pBorrowRate = p2pRate.sub(
p2pRate
.sub(params.poolBorrowRatePerYear)
.mul(params.reserveFactor)
.div(WadRayMath.RAY)
);
}
if (params.p2pDelta.gt(0) && params.p2pAmount.gt(0)) {
const a = params.p2pDelta
.mul(params.poolIndex)
.div(params.p2pAmount.mul(params.p2pIndex));
const b = WadRayMath.RAY;
const shareOfTheDelta = a.gt(b) ? b : a;
p2pBorrowRate = p2pBorrowRate
.mul(WadRayMath.RAY.sub(shareOfTheDelta))
.div(WadRayMath.RAY)
.add(
params.poolBorrowRatePerYear.mul(shareOfTheDelta).div(WadRayMath.RAY)
);
}
return p2pBorrowRate;
};
/**
* This function compute the borrow rate on a specific asset and returns the result.
*
* @param underlying The market to retrieve the borrow APY.
* @param provider A provider instance
*
* @returns The P2P borrow rate per year and the pool borrow rate per year in _RAY_ units.
*/
const getBorrowRatesPerYear = async (
underlying: string,
provider: providers.BaseProvider
) => {
const { morphoAaveV3, pool } = getContracts(provider);
const [
{ currentLiquidityRate, currentVariableBorrowRate },
{
deltas: {
borrow: { scaledDelta, scaledP2PTotal },
},
indexes: {
borrow: { p2pIndex, poolIndex },
},
reserveFactor,
p2pIndexCursor,
},
] = await Promise.all([
pool.getReserveData(underlying),
morphoAaveV3.market(underlying),
]);
const p2pBorrowRate = await getP2PBorrowRate({
poolSupplyRatePerYear: currentLiquidityRate,
poolBorrowRatePerYear: currentVariableBorrowRate,
poolIndex,
p2pIndex,
proportionIdle: constants.Zero,
p2pDelta: scaledDelta,
p2pAmount: scaledP2PTotal,
p2pIndexCursor: BigNumber.from(p2pIndexCursor),
reserveFactor: BigNumber.from(reserveFactor),
});
return {
p2pBorrowRate,
poolBorrowRate: currentVariableBorrowRate,
};
};
/// UTILS ///
/**
* This function Executes a weighted average (x * (1 - p) + y * p), rounded up and returns the result.
*
* @param x The first value, with a weight of 1 - percentage.
* @param y The second value, with a weight of percentage.
* @param percentage The weight of y, and complement of the weight of x.
* @returns The result of the weighted average.
*/
const getWeightedAvg = (x: BigNumber, y: BigNumber, percentage: BigNumber) => {
const MAX_UINT256_MINUS_HALF_PERCENTAGE_FACTOR = constants.MaxUint256.sub(
PercentMath.HALF_PERCENT
);
let z: BigNumber = PercentMath.BASE_PERCENT.sub(percentage);
if (
percentage.gt(PercentMath.BASE_PERCENT) ||
(percentage.gt(0) &&
y.gt(MAX_UINT256_MINUS_HALF_PERCENTAGE_FACTOR.div(percentage))) ||
(PercentMath.BASE_PERCENT.gt(percentage) &&
x.gt(
MAX_UINT256_MINUS_HALF_PERCENTAGE_FACTOR.sub(y.mul(percentage)).div(z)
))
) {
throw new Error("Underflow or overflow detected");
}
z = x
.mul(z)
.add(y.mul(percentage))
.add(PercentMath.HALF_PERCENT)
.div(PercentMath.BASE_PERCENT);
return z;
};
/**
* This function is computing an average rate
* and returns the weighted rate and the total balance.
*
* @param p2pRate The peer-to-peer rate per year, in _RAY_ units
* @param poolRate The pool rate per year, in _RAY_ units
* @param balanceInP2P The underlying balance matched peer-to-peer
* @param balanceOnPool The underlying balance on the pool
*/
const getWeightedRate = async (
p2pRate: BigNumber,
poolRate: BigNumber,
balanceInP2P: BigNumber,
balanceOnPool: BigNumber
) => {
const totalBalance = balanceInP2P.add(balanceOnPool);
if (totalBalance.isZero())
return {
weightedRate: constants.Zero,
totalBalance,
};
return {
weightedRate: p2pRate
.mul(balanceInP2P)
.add(poolRate.mul(balanceOnPool))
.div(totalBalance),
totalBalance,
};
};
/**
* This function is subtracting one number from another, but ensuring that the result is never negative, instead of returning a negative value it will return zero.
*
* @param a A BigNumber.
* @param b A BigNumber.
* @returns A non negative number or 0.
*/
const zeroFloorSub = (a: BigNumber, b: BigNumber) =>
maxBN(constants.Zero, a.sub(b));
// SPDX-License-Identifier: GNU AGPLv3
pragma solidity ^0.8.16;
import {ILens} from "./interfaces/ILens.sol";
import {IMorpho} from "./interfaces/IMorpho.sol";
import {IPriceOracleGetter} from "./interfaces/aave/IPriceOracleGetter.sol";
import {WadRayMath} from "@morpho-org/morpho-utils/src/math/WadRayMath.sol";
contract MorphoAaveV2Borrower {
using WadRayMath for uint256;
address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address public constant ADAI = 0x028171bCA77440897B824Ca71D1c56caC55b68A3;
address public constant AWBTC = 0x9ff58f4fFB29fA2266Ab25e75e2A8b3503311656;
address public constant LENS = 0x507fA343d0A90786d86C7cd885f5C49263A91FF4;
address public constant MORPHO = 0x777777c9898D384F785Ee44Acfe945efDFf5f3E0;
ICompoundOracle public immutable ORACLE;
constructor() {
ORACLE = IPriceOracleGetter(
IMorpho(MORPHO).addressesProvider().getPriceOracle()
);
}
/// @notice Returns the distribution of WBTC borrowed by this contract through Morpho AaveV2 Optimizer.
/// @return borrowedOnPool The amount of WBTC borrowed on AaveV2's pool (with 8 decimals, the number of decimals of WBTC).
/// @return borrowedP2P The amount of WBTC borrowed peer-to-peer through Morpho AaveV2 Optimizer (with 8 decimals, the number of decimals of WBTC).
function getWBTCBorrowBalance()
public
view
returns (uint256 borrowedOnPool, uint256 borrowedP2P)
{
(borrowedOnPool, borrowedP2P, ) = ILens(LENS)
.getCurrentBorrowBalanceInOf(
AWBTC, // the WBTC market, represented by the aWBTC 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 AaveV2 Optimizer.
/// @return borrowedOnPoolDAI The DAI amount of WBTC borrowed on AaveV2's pool (with 18 decimals, the number of decimals of DAI).
/// @return borrowedP2PDAI The DAI amount of WBTC borrowed peer-to-peer through Morpho AaveV2 Optimizer (with 18 decimals, the number of decimals of DAI).
function getWBTCBorrowBalanceDAI()
public
view
returns (uint256 borrowedOnPoolDAI, uint256 borrowedP2PDAI)
{
(uint256 borrowedOnPool, uint256 borrowedP2P) = getWBTCBorrowBalance();
uint256 oraclePrice = ORACLE.getAssetPrice(DAI); // with 18 decimals, whatever the asset
borrowedOnPoolDAI = borrowedOnPool.wadMul(oraclePrice); // with 18 decimals, the number of decimals of DAI
borrowedP2PDAI = borrowedP2P.wadMul(oraclePrice); // with 18 decimals, the number of decimals of DAI
}
/// @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 AaveV2 pool.
/// @return The APR at which borrow interests are accumulated on average on the DAI market (with 27 decimals).
function getDAIAvgBorrowAPR() public view returns (uint256) {
return
ILens(LENS).getAverageBorrowRatePerYear(
ADAI // the DAI market, represented by the cDAI ERC20 token
);
}
/// @notice Returns the expected APR at which borrow interests are accrued by this contract, 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 AaveV2 pool.
/// @return The APR at which WBTC borrow interests are accrued (with 27 decimals).
function getWBTCBorrowAPR() public view returns (uint256) {
return
ILens(LENS).getCurrentUserBorrowRatePerYear(
AWBTC, // the WBTC market, represented by the aWBTC ERC20 token
address(this) // the address of the user you want to know the borrow rate of
);
}
/// @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 AaveV2 pool.
/// @return nextSupplyAPR The APR at which borrow interests are accrued by this contract on the WBTC market (with 27 decimals).
function getWBTCNextSupplyAPR(uint256 _amount)
public
view
returns (uint256 nextSupplyAPR)
{
(nextSupplyAPR, , , ) = ILens(LENS).getNextUserSupplyRatePerYear(
AWBTC, // the WBTC market, represented by the aWBTC ERC20 token
address(this), // the address of the user you want to know the next supply rate of
_amount
);
}
/// @notice Returns the expected amount of borrow interests accrued by this contract, on the WBTC market, after `_nbSeconds`.
/// @return The expected amount of WBTC borrow interests accrued (in 8 decimals, the number of decimals of WBTC).
function getWBTCExpectedAccruedInterests(uint256 _nbSeconds)
public
view
returns (uint256)
{
(uint256 borrowedOnPool, uint256 borrowedP2P) = getWBTCBorrowBalance();
uint256 borrowRatePerYear = getWBTCBorrowAPR();
return
((borrowedOnPool + borrowedP2P).rayMul(borrowRatePerYear) *
_nbSeconds) / 365.25 days;
}
/// @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
) <= 1.05e18;
}
}
import ethers from "ethers";
const signer = new ethers.Wallet(
process.env.PRIVATE_KEY,
new ethers.providers.JsonRpcBatchProvider(process.env.RPC_URL)
);
const signerAddress = await signer.getAddress();
const daiAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
const wbtcAddress = "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599";
const aWethAddress = "0x030bA81f1c18d280636F32af80b9AAd02Cf0854e";
const aDaiAddress = "0x028171bCA77440897B824Ca71D1c56caC55b68A3";
const aWbtcAddress = "0x9ff58f4fFB29fA2266Ab25e75e2A8b3503311656";
const wbtcDecimals = 8;
const daiDecimals = 18;
const dai = new ethers.Contract(
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
DAIAbi,
signer
);
const weth = new ethers.Contract(
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
WETH9Abi,
signer
);
const lens = new ethers.Contract(
"0x507fA343d0A90786d86C7cd885f5C49263A91FF4",
LensAbi,
signer
);
const morpho = new ethers.Contract(
"0x777777c9898d384f785ee44acfe945efdff5f3e0",
MorphoAbi,
signer
);
const oracle = new ethers.Contract(
"0xA50ba011c48153De246E5192C8f9258A2ba79Ca9",
OracleAbi,
signer
);
/// QUERY ///
async function getTotalBorrowETH() {
const [, , totalBorrowETH] = await lens.getTotalBorrow();
return Number(ethers.utils.formatUnits(totalBorrowETH, 18)); // ETH amounts are always in 18 decimals
}
async function getTotalBorrowDAI() {
const totalBorrowETH = await getTotalBorrowETH();
const daiOraclePrice = await oracle.getAssetPrice(daiAddress); // in ETH (18 decimals), whatever the market
return totalBorrowETH / Number(ethers.utils.formatUnits(daiOraclePrice, 18)); // ETH amounts are always in 18 decimals
}
async function getTotalDAIMarketBorrow() {
const [borrowedP2P, borrowedOnPool] = await lens.getTotalMarketBorrow(
aDaiAddress // the DAI market, represented by the aDAI ERC20 token
);
return Number(
ethers.utils.formatUnits(borrowedP2P.add(borrowedOnPool), daiDecimals)
);
}
async function getWBTCBorrowBalance() {
const [borrowedOnPool, borrowedP2P] = await lens.getCurrentBorrowBalanceInOf(
aWbtcAddress, // the WBTC market, represented by the aWBTC ERC20 token
signerAddress // the address of the user you want to get the borrow of
);
return Number(
ethers.utils.formatUnits(borrowedP2P.add(borrowedOnPool), wbtcDecimals)
);
}
async function getWBTCBorrowBalanceETH() {
const totalMarketBorrow = await getWBTCBorrowBalance();
const wbtcOraclePrice = await oracle.getAssetPrice(wbtcAddress); // in ETH (18 decimals), whatever the market
return (
totalMarketBorrow * Number(ethers.utils.formatUnits(wbtcOraclePrice, 18))
);
}
async function getWBTCBorrowBalanceDAI() {
const marketBorrowETH = await getWBTCBorrowBalanceETH();
const daiOraclePrice = await oracle.getAssetPrice(daiAddress); // in ETH (18 decimals), whatever the market
return marketBorrowETH * Number(ethers.utils.formatUnits(daiOraclePrice, 18));
}
// @note The borrow rate experienced on a market is specific to each user,
// @note dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
async function getDAIAvgBorrowAPR() {
const [avgBorrowRatePerYear] = await lens.getAverageBorrowRatePerYear(
aDaiAddress // the DAI market, represented by the aDAI ERC20 token
);
return Number(ethers.utils.formatUnits(avgBorrowRatePerYear, 27)); // 18 decimals, whatever the market
}
// @note The borrow rate experienced on a market is specific to each user,
// @note dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
async function getWBTCBorrowAPR() {
const borrowRatePerYear = await lens.getCurrentUserBorrowRatePerYear(
aWbtcAddress, // the DAI market, represented by the aDAI ERC20 token
signerAddress // the address of the user you want to get the borrow rate of
);
return Number(ethers.utils.formatUnits(borrowRatePerYear, 27)); // 18 decimals, whatever the market
}
// @note The borrow rate experienced on a market is specific to each user,
// @note dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
async function getWBTCNextBorrowAPR(amount) {
const [nextBorrowRatePerYear] = await lens.getNextUserBorrowRatePerYear(
aWbtcAddress, // the DAI market, represented by the aDAI ERC20 token
signerAddress, // the address of the user you want to get the next borrow rate of
amount
);
return Number(ethers.utils.formatUnits(nextBorrowRatePerYear, 27)); // 18 decimals, whatever the market
}
getTotalBorrowETH().then((val) => console.log("Total borrow ETH", val));
getTotalBorrowDAI().then((val) => console.log("Total borrow DAI", val));
getTotalDAIMarketBorrow().then((val) => console.log("DAI borrow", val));
getWBTCBorrowBalance().then((val) => console.log("WBTC own borrow", val));
getWBTCBorrowBalanceETH().then((val) =>
console.log("WBTC own borrow ETH", val)
);
getWBTCBorrowBalanceDAI().then((val) =>
console.log("WBTC own borrow DAI", val)
);
getDAIAvgBorrowAPR().then((val) => console.log("DAI avg borrow APR", val));
getWBTCBorrowAPR().then((val) => console.log("WBTC borrow APR", val));
getWBTCNextBorrowAPR(ethers.utils.parseUnits("100", wbtcDecimals)).then((val) =>
console.log("WBTC next borrow rate", val)
);
Morpho CompoundV2 Optimizer​
- Solidity
- ethers.js
// 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;
}
}
import ethers from "ethers";
const signer = new ethers.Wallet(
process.env.PRIVATE_KEY,
new ethers.providers.JsonRpcBatchProvider(process.env.RPC_URL)
);
const signerAddress = await signer.getAddress();
const cDaiAddress = "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643";
const cWbtc2Address = "0xccF4429DB6322D5C611ee964527D42E5d685DD6a";
const lens = new ethers.Contract(
"0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67",
[
"function getTotalBorrow() external view returns (uint256, uint256, uint256)",
"function getTotalMarketBorrow(address) external view returns (uint256, uint256)",
"function getCurrentBorrowBalanceInOf(address, address) external view returns (uint256, uint256, uint256)",
"function getAverageBorrowRatePerBlock(address) external view returns (uint256, uint256, uint256)",
"function getCurrentUserBorrowRatePerBlock(address, address) external view returns (uint256)",
"function getNextUserBorrowRatePerBlock(address, address, uint256) external view returns (uint256, uint256, uint256, uint256)",
],
signer
);
const oracle = new ethers.Contract(
"0x65c816077C29b557BEE980ae3cC2dCE80204A0C5",
["function getUnderlyingPrice(address) external view returns (uint256)"],
signer
);
async function getTotalBorrowUSD() {
const [, , totalBorrowUSD] = await lens.getTotalBorrow();
return Number(ethers.utils.formatUnits(totalBorrowUSD, 18)); // USD amounts are always in 18 decimals
}
async function getTotalDAIMarketBorrow() {
const [borrowedP2P, borrowedOnPool] = await lens.getTotalMarketBorrow(
cDaiAddress // the DAI market, represented by the cDAI ERC20 token
);
return Number(
ethers.utils.formatUnits(borrowedP2P.add(borrowedOnPool), daiDecimals)
);
}
async function getWBTCBorrowBalance() {
const [borrowedOnPool, borrowedP2P] = await lens.getCurrentBorrowBalanceInOf(
cWbtc2Address, // the WBTC market, represented by the cWBTC2 ERC20 token
signerAddress // the address of the user you want to get the borrow of
);
return Number(
ethers.utils.formatUnits(borrowedP2P.add(borrowedOnPool), wbtcDecimals)
);
}
async function getWBTCBorrowBalanceUSD() {
const totalMarketBorrow = await getWBTCBorrowBalance();
const oraclePrice = await oracle.getUnderlyingPrice(cWbtc2Address); // in (36 - nb decimals of WBTC = 28) decimals
return (
totalMarketBorrow *
Number(ethers.utils.formatUnits(oraclePrice, 36 - wbtcDecimals))
);
}
const nbBlocksPerYear = 4 * 60 * 24 * 365.25;
// @note The borrow rate experienced on a market is specific to each user,
// @note dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
async function getDAIAvgBorrowAPR() {
const [avgBorrowRatePerBlock] = await lens.getAverageBorrowRatePerBlock(
cDaiAddress // the DAI market, represented by the cDAI ERC20 token
);
return (
Number(ethers.utils.formatUnits(avgBorrowRatePerBlock, 18)) * // 18 decimals, whatever the market
nbBlocksPerYear
);
}
// @note The borrow rate experienced on a market is specific to each user,
// @note dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
async function getWBTCBorrowAPR() {
const borrowRatePerBlock = await lens.getCurrentUserBorrowRatePerBlock(
cWbtc2Address, // the DAI market, represented by the cDAI ERC20 token
signerAddress // the address of the user you want to get the borrow rate of
);
return (
Number(ethers.utils.formatUnits(borrowRatePerBlock, 18)) * // 18 decimals, whatever the market
nbBlocksPerYear
);
}
// @note The borrow rate experienced on a market is specific to each user,
// @note dependending on how their borrow is matched peer-to-peer or borrowed to the Compound pool.
async function getWBTCNextBorrowAPR(amount) {
const [nextBorrowRatePerBlock] = await lens.getNextUserBorrowRatePerBlock(
cWbtc2Address, // the DAI market, represented by the cDAI ERC20 token
signerAddress, // the address of the user you want to get the next borrow rate of
amount
);
return (
Number(ethers.utils.formatUnits(nextBorrowRatePerBlock, 18)) * // 18 decimals, whatever the market
nbBlocksPerYear
);
}
getTotalBorrowUSD().then((val) => console.log("Total borrow USD", val));
getTotalDAIMarketBorrow().then((val) => console.log("DAI borrow", val));
getWBTCBorrowBalance().then((val) => console.log("WBTC own borrow", val));
getWBTCBorrowBalanceUSD().then((val) =>
console.log("WBTC own borrow USD", val)
);
getDAIAvgBorrowAPR().then((val) => console.log("DAI avg borrow APR", val));
getWBTCBorrowAPR().then((val) => console.log("WBTC borrow APR", val));
getWBTCNextBorrowAPR(ethers.utils.parseUnits("100", wbtcDecimals)).then((val) =>
console.log("WBTC next borrow rate", val)
);