Track Borrow Positions
Learn how to track any position borrowed through Morpho, on-chain via a Smart Contract & off-chain via ethers.js.
Last updated
Learn how to track any position borrowed through Morpho, on-chain via a Smart Contract & off-chain via ethers.js.
Last updated
This documentation is provided by MorphoLabs, main contributor to Morpho DAO. For any question, reach out.
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-V3 & Morpho-Aave-V2 are Deployed.
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-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);
}
}
}
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.
/// @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 (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.
/// @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 (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)
);
// 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;
}
}
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";