calculateUserAccountData

This function calculates and returns the following user data across all the assets:
totalCollateralInBaseCurrency
totalDebtInBaseCurrency
avgLTV
avgLiquidationThreshold
healthFactor
hasZeroLtvCollateral
Visual Aid
check if userConfig
is empty
userConfig
is empty if (params.userConfig.isEmpty()) {
return (0, 0, 0, 0, type(uint256).max, false);
}

If all the bits in UserConfiguration is set to 0, data == 0
will evaluate to be true. This indicates that the user did not undertake any borrow or supply as collateral action.
returns health factor as
type(uint256).max
returns
hasZeroLTVCollateral
asfalse
If user is in e-mode
We need to obtain e-mode specific info such as LTV, liquidationThreshold and eModeAssetPrice.
if (params.userEModeCategory != 0) {
(vars.eModeLtv, vars.eModeLiqThreshold, vars.eModeAssetPrice) = EModeLogic
.getEModeConfiguration(
eModeCategories[params.userEModeCategory],
IPriceOracleGetter(params.oracle)
);
}

Get e-mode configuration details, by passing the user's eModeCategory id (params.userEModeCategory
) into the mapping eModeCategories.
This will return the EModeCategory
struct.

params.userEModeCategory
was obtained previously by passing _usersEModeCategory[msg.sender]

The if statement exists purely to check if a priceSource
was defined in EModeCategory
. If no priceSource
was defined, params.oracle
is returned as the oracle address.
Else params.oracle
, is overwritten with the category.priceSource
.
While loop
while (vars.i < params.reservesCount) {...}
Loops through all the active reserves there are in the protocol. reservesCount
is defined on PoolStorage.sol
// Maximum number of active reserves there have been in the protocol. It is the upper bound of the reserves list
uint16 internal _reservesCount;
For each asset, the following is executed.
1. Check if asset isUsingAsCollateralOrBorrowing

If the asset is not being used as either, increment the counter and continue
; skip the remaining block of code and moving to the next reserveIndex
.
isUsingAsCollateralOrBorrowing

require statement performs a boundary check to ensure that
reserveIndex
value is within the valid range of[0 - 127]
.If you are unclear on the bitmap manipulations, please see that section.
2. Check if zero address
// reservesList: List of reserves as a map (reserveId => reserve).
vars.currentReserveAddress = reservesList[vars.i];
if (vars.currentReserveAddress == address(0)) {
unchecked {
++vars.i;
}
continue;
}
Get the asset address of the current iteration by passing the counter into mapping reservesList
reservesList is defined on PoolStorage
If asset address is undefined, increment counter and continue.
Would there be gas savings by checking for zero address first, then followed by isUsingAsCollateralOrBorrow?
3. Get asset's params
Now that we have established that the asset is defined and being used by the user as either collateral or borrowing, let us obtain the following key details:
LTV
liquidationThreshold
decimals
Emode category

This is achieved via getParams
, which utilizes bitmasks to extract the relevant information from the ReserveConfigurationMap; which is a bitmap.

4. Set decimals and oracle interface
Define the decimal precision of 1 unit if the asset (
1 Ether = 10**18
|1 USDC = 10**6
)Define the oracle interface

If both the user and the asset are in the same e-mode category, and vars.eModeAssetPrice !=0
, use vars.eModeAssetPrice
// this was set earlier via getEModeConfiguration
vars.eModeAssetPrice = IPriceOracleGetter(params.oracle).getAssetPrice(eModePriceSource)
Else, default to using the following as the oracle interface:
IPriceOracleGetter(params.oracle).getAssetPrice(vars.currentReserveAddress);
5. If asset is used as collateral
If the asset's liquidation threshold is defined and it is being used by the user as collateral, execute the following.

get user's balance in base CCY and increment
totalCollateralInBaseCurrency
totalCollateralInBaseCurrency
will be the sum of collateral across all asset classes, normalized into the base currency.E.g. get user's total collateral in USD.
// if user and asset in same emode category, returns true
vars.isInEModeCategory = EModeLogic.isInEModeCategory(params.userEModeCategory, vars.eModeAssetCategory);
If the asset's LTV is defined: avgLTV

calculate the user's max debt for each asset
the sum of these across all assets will give us the numerator for the avgLTV calculation
we will divide by
totalCollateralInBaseCurrency
at the end
avgLiquidationThreshold
For each wallet, the Liquidation Threshold is calculate as the weighted average of the Liquidation Thresholds of the collateral assets and their value:
vars.avgLiquidationThreshold += vars.userBalanceInBaseCurrency * (vars.isInEModeCategory ? vars.eModeLiqThreshold : vars.liquidationThreshold);
At this stage we simply look to obtain the numerator for the avgLiquidationThreshold calculation. Like avgLTV, the division will be done at the end, once the loop has been completed.
6. isBorrowing

If user is borrowing this asset, calculate its value in base CCY, and increment vars.totalDebtInBaseCurrency
Exit loop
Now that we have traversed across the entire universe of assets and increments the various necessary measures like
totalCollateralInBaseCurrency
totalDebtInBaseCurrency
LTV, Liquidation threshold
We have the prerequisites to calculate a wallet's health factor.

First we obtain avgLtv and avgLiquidationThreshold by dividing them each against totalCollateralInBaseCurrency
.
Then the calculation for health factor:

avgLiquidationThreshold
was obtained by dividing the weighted sum by totalCollateral, therefore this can be expressed as:
collateral * liquidationThreshold => max debt possible for that collateral asset
the ratio of the sum of max debt possible against the totalDebt presently held constitutes the health factor of a wallet
If hf < 1,
numerator < denominator
<
User's current total debt exceeds his max loan value possible; hence considered undercollateralized.
Last updated
Was this helpful?