Skip to content

Gauntlet Aera v2 - add Morpho borrow positions #15551

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 116 additions & 6 deletions projects/aera/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ const GEARBOX_TOKEN_PREFIX = 'Farming of'
const ARRAKIS_TOKEN_PREFIX = 'Arrakis Vault V2'
const ESXAI_POSITION_ORACLE_NAME = 'EsXai Position Oracle'
const SYMBIOTIC_TOKEN_PREFIX = 'Symbiotic Vault'
const MORPHO_BLUE_POSITION_ORACLE_NAME = 'MorphoBluePositionOracle'

const config = {
polygon: {
aavePool: '0x794a61358D6845594F94dc1DB02A252b5b4814aD',
aavePoolDataProvider: '0x69FA688f1Dc47d4B5d8029D5a35FB7a548310654',
morphoBlue: '0x1bF0c2541F820E775182832f06c0B7Fc27A25f67',
comets: [
{
address: '0xF25212E676D1F7F89Cd72fFEe66158f541246445',
Expand Down Expand Up @@ -44,6 +46,7 @@ const config = {
ethereum: {
aavePool: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2',
aavePoolDataProvider: '0x7B4EB56E7CD4b454BA8ff71E4518426369a138a3',
morphoBlue: '0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb',
comets: [
{
address: '0xA17581A9E3356d9A858b789D68B4d866e593aE94',
Expand Down Expand Up @@ -82,6 +85,7 @@ const config = {
arbitrum: {
aavePool: '0x794a61358D6845594F94dc1DB02A252b5b4814aD',
aavePoolDataProvider: '0x6b4E260b765B3cA1514e618C0215A6B7839fF93e',
morphoBlue: '0x6c247b1F6182318877311737BaC0844bAa518F5e',
comets: [
{
address: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA',
Expand Down Expand Up @@ -111,6 +115,7 @@ const config = {
base: {
aavePool: '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5',
aavePoolDataProvider: '0x2d8A3C5677189723C4cB8873CfC9C8976FDF38Ac',
morphoBlue: '0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb',
comets: [
{
address: '0x46e6b214b524310239732D51387075E0e70970bf',
Expand Down Expand Up @@ -148,6 +153,7 @@ Object.keys(config).forEach(chain => {
tvl: async (api) => {
const AAVE_POOL = config[chain].aavePool
const AAVE_POOL_DATA_PROVIDER = config[chain].aavePoolDataProvider
const MORPHO_BLUE = config[chain].morphoBlue
const COMETS = config[chain].comets
const COMET_REWARD = config[chain].cometReward
const ARRAKIS_HELPER = config[chain].arrakisHelper
Expand Down Expand Up @@ -192,7 +198,7 @@ Object.keys(config).forEach(chain => {
const xaiPositionVaults = []
const esXaiVaults = []
const symbioticVaults = []

const morphoBlueVaults = []
for (let i = 0; i < vaults.length; ++i) {
const vault = vaults[i]
for (let j = 0; j < assets[i].length; ++j) {
Expand Down Expand Up @@ -228,6 +234,10 @@ Object.keys(config).forEach(chain => {
symbioticVaults.push([vault, assetInfo.asset])
continue
}
if (assetName === MORPHO_BLUE_POSITION_ORACLE_NAME) {
morphoBlueVaults.push([vault, assetInfo.asset])
continue
}
}


Expand Down Expand Up @@ -263,7 +273,8 @@ Object.keys(config).forEach(chain => {
processGearboxTvl(gearboxFarmingPools, api),
processArrakisTvl(arrakisVaults, api, ARRAKIS_HELPER),
processXaiTvl(xaiPositionVaults, api, ESXAI_POOL_FACTORY, XAI),
processSymbioticTvl(symbioticVaults, api)
processSymbioticTvl(symbioticVaults, api),
processMorphoBlueTvl(morphoBlueVaults, api, MORPHO_BLUE)
])

Object.keys(erc4626UnderylingMap).forEach((erc4626Asset, i) => erc4626UnderylingMap[erc4626Asset] = underlyingTokens[i])
Expand All @@ -282,7 +293,7 @@ Object.keys(config).forEach(chain => {

async function processXaiTvl(xaiPositionVaults, api, ESXAI_POOL_FACTORY, XAI) {
if (xaiPositionVaults.length === 0) return

const pools = await api.multiCall({ abi: abi.getPoolIndicesOfUser, calls: xaiPositionVaults.map(x => ({target: ESXAI_POOL_FACTORY, params: [x]}))})
const vaultPools = xaiPositionVaults.flatMap((vault, i) => pools[i].map(pool => ([vault, pool])))

Expand Down Expand Up @@ -310,7 +321,7 @@ async function processArrakisTvl(arrakisVaults, api, arrakisHelper) {
api.multiCall({ abi: abi.token1, calls: arrakisVaults}),
api.multiCall({ abi: abi.totalUnderlying, calls: arrakisVaults.map(x => ({target: arrakisHelper, params: [x]}))})
])

totalUnderlyings.forEach((v, i) => {
api.addToken(tokens0[i], v.totalAmount0)
api.addToken(tokens1[i], v.totalAmount1)
Expand Down Expand Up @@ -413,7 +424,7 @@ async function processAaveTvl(aaveVaults, api, AAVE_POOL, AAVE_POOL_DATA_PROVIDE
api.addToken(aaveReserveDetails[reserveIdx].stableDebtTokenAddress, aavePosition.currentStableDebt);
api.addToken(aaveReserveDetails[reserveIdx].variableDebtTokenAddress, aavePosition.currentVariableDebt);
}

}

async function processSymbioticTvl(symbioticVaults, api) {
Expand All @@ -428,6 +439,100 @@ async function processSymbioticTvl(symbioticVaults, api) {
})
}

async function processMorphoBlueTvl(morphoBlueVaults, api, MORPHO_BLUE) {
if (morphoBlueVaults.length === 0) return;

// Get the number of markets for each oracle
const marketLengths = await api.multiCall({
abi: abi.getMarketsLength,
calls: morphoBlueVaults.map(x => ({target: x[1], params: []}))
});


// Build calls to get each market by index
const marketCalls = [];
morphoBlueVaults.forEach(([vault, oracle], oracleIndex) => {
const length = marketLengths[oracleIndex];
for (let i = 0; i < length; i++) {
marketCalls.push({
target: oracle,
params: [i],
vault: vault,
oracleIndex: oracleIndex
});
}
});

if (marketCalls.length === 0) return;

// Get all markets from the Morpho Blue position oracles
const markets = await api.multiCall({ abi: abi.markets, calls: marketCalls });

const [marketInfos, marketParams] = await Promise.all(
[
api.multiCall({
abi: abi.market,
calls: markets.map(market => ({target: MORPHO_BLUE, params: [market[0]]}))
}),
api.multiCall({
abi: abi.idToMarketParams,
calls: markets.map(market => ({ target: MORPHO_BLUE, params: [market[0]] }))
})
]
);

// Get positions for each vault across all markets
const positionCalls = [];
marketCalls.forEach((call, index) => {
const marketId = markets[index][0]; // Extract the market ID (bytes32)
positionCalls.push({
target: MORPHO_BLUE,
params: [marketId, call.vault]
});
});

const positions = await api.multiCall({
abi: abi.position,
calls: positionCalls
});

// Process positions and add to TVL
await Promise.all(positionCalls.map(async (call, index) => {
const [_, vault] = call.params;
const position = positions[index];
const marketParam = marketParams[index];
const marketInfo = marketInfos[index];

if (position.collateral > 0) {
try {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove any try/catch

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you propose an alternative? Some Morpho positions use collateral in ERC4626 contracts. In order to account for the collateral for these positions we need to get the underlying asset and convert shares to assets. But invoking ERC4626 functions on contracts that do not support these methods will revert. Without this logic we account for the borrowed asset but not the collateral, which results in a large negative position.

// If the collateral token is an ERC4626, we need to convert it to the underlying asset
const [underlyingAsset, collateralAssets] = await Promise.all([
api.call({ abi: 'address:asset', target: marketParam.collateralToken }),
api.call({ abi: abi.convertToAssets, target: marketParam.collateralToken, params: [position.collateral] })
]);
api.addToken(underlyingAsset, collateralAssets, vault);
} catch (e) {
api.addToken(marketParam.collateralToken, position.collateral, vault);
}
}

if (position.supplyShares > 0) {
const supplyAssets = (
BigInt(position.supplyShares) *
BigInt(marketInfo.totalSupplyAssets) / BigInt(marketInfo.totalSupplyShares)
);
api.addToken(marketParam.loanToken, supplyAssets, vault);
}
if (position.borrowShares > 0) {
const borrowAssets = (
BigInt(position.borrowShares) *
BigInt(marketInfo.totalBorrowAssets) / BigInt(marketInfo.totalBorrowShares)
);
api.addToken(marketParam.loanToken, -borrowAssets, vault);
}
}));
}

const abi = {
"collateralBalanceOf": "function collateralBalanceOf(address account, address asset) view returns (uint128)",
"getReserveData": "function getReserveData(address asset) view returns (((uint256 data) configuration, uint128 liquidityIndex, uint128 currentLiquidityRate, uint128 variableBorrowIndex, uint128 currentVariableBorrowRate, uint128 currentStableBorrowRate, uint40 lastUpdateTimestamp, uint16 id, address aTokenAddress, address stableDebtTokenAddress, address variableDebtTokenAddress, address interestRateStrategyAddress, uint128 accruedToTreasury, uint128 unbacked, uint128 isolationModeTotalDebt))",
Expand Down Expand Up @@ -458,5 +563,10 @@ const abi = {
"esXaiStakeBucket": "function esXaiStakeBucket() returns (address)",
"getStakedAmounts": "function getStakedAmounts(address) returns (uint256)",
"withdrawableDividendOf": "function withdrawableDividendOf(address) returns (uint256)",
"collateral": "function collateral() returns (address)"
"collateral": "function collateral() returns (address)",
"getMarketsLength": "function getMarketsLength() view returns (uint256)",
"markets": "function markets(uint256) view returns ((bytes32, address, uint80, uint8, bool))",
"idToMarketParams": "function idToMarketParams(bytes32) view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)",
"position": "function position(bytes32, address) view returns (uint256 supplyShares, uint256 borrowShares, uint256 collateral)",
"market": "function market(bytes32) view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares)"
}
Loading