calculateUserAccountData 是Aave V3协议中计算用户账户数据的核心方法,在整个借贷过程中,健康因子监控,借款限额,健康因子监控等多个环节都会涉及到。
function calculateUserAccountData(
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
DataTypes.CalculateUserAccountDataParams memory params
) internal view returns (uint256, uint256, uint256, uint256, uint256, bool) {
if (params.userConfig.isEmpty()) {
return (0, 0, 0, 0, type(uint256).max, false);
}
CalculateUserAccountDataVars memory vars;
if (params.userEModeCategory != 0) {
vars.eModeLtv = eModeCategories[params.userEModeCategory].ltv;
vars.eModeLiqThreshold = eModeCategories[params.userEModeCategory].liquidationThreshold;
vars.eModeCollateralBitmap = eModeCategories[params.userEModeCategory].collateralBitmap;
}
uint256 userConfigCache = params.userConfig.data;
bool isBorrowed = false;
bool isEnabledAsCollateral = false;
while (userConfigCache != 0) {
(userConfigCache, isBorrowed, isEnabledAsCollateral) = UserConfiguration.getNextFlags(
userConfigCache
);
if (isEnabledAsCollateral || isBorrowed) {
vars.currentReserveAddress = reservesList[vars.i];
if (vars.currentReserveAddress != address(0)) {
DataTypes.ReserveData storage currentReserve = reservesData[vars.currentReserveAddress];
(vars.ltv, vars.liquidationThreshold, , vars.decimals, ) = currentReserve
.configuration
.getParams();
unchecked {
vars.assetUnit = 10 ** vars.decimals;
}
vars.assetPrice = IPriceOracleGetter(params.oracle).getAssetPrice(
vars.currentReserveAddress
);
if (vars.liquidationThreshold != 0 && isEnabledAsCollateral) {
vars.userBalanceInBaseCurrency = _getUserBalanceInBaseCurrency(
params.user,
currentReserve,
vars.assetPrice,
vars.assetUnit
);
vars.totalCollateralInBaseCurrency += vars.userBalanceInBaseCurrency;
vars.isInEModeCategory =
params.userEModeCategory != 0 &&
EModeConfiguration.isReserveEnabledOnBitmap(vars.eModeCollateralBitmap, vars.i);
if (vars.ltv != 0) {
vars.avgLtv +=
vars.userBalanceInBaseCurrency *
(vars.isInEModeCategory ? vars.eModeLtv : vars.ltv);
} else {
vars.hasZeroLtvCollateral = true;
}
vars.avgLiquidationThreshold +=
vars.userBalanceInBaseCurrency *
(vars.isInEModeCategory ? vars.eModeLiqThreshold : vars.liquidationThreshold);
}
if (isBorrowed) {
vars.totalDebtInBaseCurrency += _getUserDebtInBaseCurrency(
params.user,
currentReserve,
vars.assetPrice,
vars.assetUnit
);
}
}
}
unchecked {
++vars.i;
}
}
// @note At this point, `avgLiquidationThreshold` represents
// `SUM(collateral_base_value_i * liquidation_threshold_i)` for all collateral assets.
// It has 8 decimals (base currency) + 2 decimals (percentage) = 10 decimals.
// healthFactor has 18 decimals
// healthFactor = (avgLiquidationThreshold * WAD / totalDebtInBaseCurrency) / 100_00
// 18 decimals = (10 decimals * 18 decimals / 8 decimals) / 2 decimals = 18 decimals
vars.healthFactor = (vars.totalDebtInBaseCurrency == 0)
? type(uint256).max
: vars.avgLiquidationThreshold.wadDiv(vars.totalDebtInBaseCurrency) / 100_00;
unchecked {
vars.avgLtv = vars.totalCollateralInBaseCurrency != 0
? vars.avgLtv / vars.totalCollateralInBaseCurrency
: 0;
vars.avgLiquidationThreshold = vars.totalCollateralInBaseCurrency != 0
? vars.avgLiquidationThreshold / vars.totalCollateralInBaseCurrency
: 0;
}
return (
vars.totalCollateralInBaseCurrency,
vars.totalDebtInBaseCurrency,
vars.avgLtv,
vars.avgLiquidationThreshold,
vars.healthFactor,
vars.hasZeroLtvCollateral
);
}
该方法一共有6个返回值:
- totalCollateralInBaseCurrency : 用户所有抵押品的总价值(美元)
- totalDebtInBaseCurrency : 用户所有债务的总价值(美元)
- avgLtv : 加权平均LTV
- avgLiquidationThreshold : 加权平均清算阈值
- healthFactor : 健康因子
- hasZeroLtvCollateral : 是否有LTV为0的抵押品
首先,我们稍微回忆一下健康因子(Health Factor)和贷款价值比(LTV)基础概念:
抵押品 :1 ETH,当前价格 = $2,000
债务 :1,000 USDC(稳定币)
假设ETH的LTV = 80%,则:最大可借金额 = 抵押品价值 × LTV= $2,000 × 80% = $1,600
用户当前借款 :1,000 USDC($1,000)
剩余借款额度 :$1,600 - $1,000 = $600
这意味着用户最多还能借$600的USDC。
假设ETH的清算阈值 = 85%,则
健康因子 = (抵押品价值 × 清算阈值) / 债务价值= (2000 × 85%) / 1000= 1700 / 1000 = 1.7
HF = 1.7 > 1.0 此时为安全状态,当HF < 1.0时会被清算
1. 空配置检查
if (params.userConfig.isEmpty()) {
return (0, 0, 0, 0, type(uint256).max, false);
}
如果用户没有配置任何资产,直接返回默认值(健康因子为最大值)。
2. 效率模式(E-Mode)配置
if (params.userEModeCategory != 0) {
vars.eModeLtv = eModeCategories[params.userEModeCategory].ltv;
vars.eModeLiqThreshold = eModeCategories[params.userEModeCategory].liquidationThreshold;
vars.eModeCollateralBitmap = eModeCategories[params.userEModeCategory].collateralBitmap;
}
如果用户启用了效率模式,获取对应的LTV、清算阈值和抵押品位图。
比如:userEModeCategory 为1,代表稳定币模式,则得到如下返回:
- ltv = 90% (比普通模式更高)
- liquidationThreshold = 93% (比普通模式更高)
- collateralBitmap 代表在特定效率模式(E-Mode)类别中,哪些资产可以作为抵押品,比如userEModeCategory 为1时,返回0b101,允许USDC(位0)和USDT(位2)作为抵押品
3. 遍历用户配置
接下来是一个大循环,通过位运算遍历用户配置中的所有资产,一个个检查当前资产是否被用户作为抵押品或者被借款!
while (userConfigCache != 0) {
(userConfigCache, isBorrowed, isEnabledAsCollateral) = UserConfiguration.getNextFlags(userConfigCache);
// 处理每个资产
}
我们看下getNextFlags的处理
function getNextFlags(uint256 data) internal pure returns (uint256, bool, bool) {
bool isBorrowed = data & 1 == 1;
bool isEnabledAsCollateral = data & 2 == 2;
return (data >> 2, isBorrowed, isEnabledAsCollateral);
}
userConfigCache:前面说过这是一个 256 位的位图,用于高效存储用户在所有储备资产上的状态,每个资产占用 2 位 第 1 位表示用户是否借贷该资产;第 2 位表示用户是否将该资产用作抵押品,这里返回将输入位图右移2位后的结果,移除了当前处理的资产位对(2位),指向下一个资产的位对
isBorrowed:指示当前资产是否被借入
isEnabledAsCollateral:指示当前资产是否被用作抵押品
下面这部分是抵押品计算
if (vars.liquidationThreshold != 0 && isEnabledAsCollateral) {
vars.userBalanceInBaseCurrency = _getUserBalanceInBaseCurrency(...);
vars.totalCollateralInBaseCurrency += vars.userBalanceInBaseCurrency;
// 效率模式检查
vars.isInEModeCategory = params.userEModeCategory != 0 &&
EModeConfiguration.isReserveEnabledOnBitmap(vars.eModeCollateralBitmap, vars.i);
// LTV计算
if (vars.ltv != 0) {
vars.avgLtv += vars.userBalanceInBaseCurrency *
(vars.isInEModeCategory ? vars.eModeLtv : vars.ltv);
} else {
vars.hasZeroLtvCollateral = true;
}
// 清算阈值计算
vars.avgLiquidationThreshold += vars.userBalanceInBaseCurrency *
(vars.isInEModeCategory ? vars.eModeLiqThreshold : vars.liquidationThreshold);
}
liquidationThreshold 是清算阈值;当用户的健康因子低于此阈值时,头寸会被清算,通常比LTV高,为清算提供缓冲空间。
当清算阈值不为0并且当前用户抵押了该资产
第一步是计算vars.totalCollateralInBaseCurrency 也就是用户抵押的当前资产值多少钱(一般以美元为基础),为我们看一下_getUserBalanceInBaseCurrency这个方法:
function _getUserBalanceInBaseCurrency(
address user,
DataTypes.ReserveData storage reserve,
uint256 assetPrice,
uint256 assetUnit
) private view returns (uint256) {
uint256 balance = (
IScaledBalanceToken(reserve.aTokenAddress).scaledBalanceOf(user).getATokenBalance(
reserve.getNormalizedIncome()
)
) * assetPrice;
unchecked {
return balance / assetUnit;
}
}
总共分三步:
- 获取用户的aToken余额 - 通过 IScaledBalanceToken(reserve.aTokenAddress).scaledBalanceOf(user) 获取用户的缩放余额
- 转换为实际余额 - 使用 getATokenBalance(reserve.getNormalizedIncome()) 将缩放余额转换为实际余额(reserve.getNormalizedIncome()返回的是当前资产的liquidityIndex) ,由aave v3 存款与借款利息的计算方式 这篇文章可知二者相乘得到的是累加利息后的余额。
- 转换为基础货币价值 - 将余额乘以资产价格assetPrice(assetPrice是从预言机中获取的当前资产价格),然后除以资产单位,得到基础货币价值。
vars.totalCollateralInBaseCurrency 初始值是0,循环的过程中将用户所有的抵押资产的价值累加到其上。
vars.isInEModeCategory代表当前资产是否属于用户设置的效率模式,打个比方:
用户启用"稳定币EMode"(category=1),当前资产是ETH,则返回false,因为ETH不属于稳定币,如果当前资产是USTD则返回true
vars.avgLtv 是当前用户在aave中所有资产的平均LTV,在循环内部累加用户在当前资产上抵押的金额和当前资产LTV的乘积,也就是
vars.avgLtv += vars.userBalanceInBaseCurrency * (vars.isInEModeCategory ? vars.eModeLtv : vars.ltv);
如果当前资产属于当前的效率模式,那么乘以当前效率的LTV否则乘以资产的LTV
如果LTV为0,表示该资产不能作为抵押品进行借贷那么设置vars.isInEModeCategory为1
vars.isInEModeCategory标着当前用户是否有抵押了已经不能被抵押的资产。
vars.avgLiquidationThreshold代表当前用户在aave中所有资产的平均清算阈值,计算方法和avgLtv 一致,在循环中只做累加
vars.avgLiquidationThreshold +=vars.userBalanceInBaseCurrency *
(vars.isInEModeCategory ? vars.eModeLiqThreshold : vars.liquidationThreshold);
vars.totalDebtInBaseCurrency是累计用户在所有资产上的借款,换算成美金为单位,其计算过程和_getUserBalanceInBaseCurrency方法完全一致,这里不再赘述。
vars.healthFactor: 是当前用户所有资产总和的健康因子
vars.healthFactor = (vars.totalDebtInBaseCurrency == 0)
? type(uint256).max
: vars.avgLiquidationThreshold.wadDiv(vars.totalDebtInBaseCurrency) / 100_00;
如果vars.totalDebtInBaseCurrency == 0代表当前用户没有任何债务,健康因子设为 type(uint256).max (最大值),表示用户账户处于绝对安全状态,没有清算风险。否则,使用清算阈值除以用户所有的负债得出健康因子。举例:
- 用户抵押$10,000 ETH,借款$5,000
- 加权平均清算阈值 = $10,000 × 80% = $8,000
- 健康因子 = $8,000 ÷ $5,000 = 1.6
由于此时的健康因子大于1,代表当前的用户状态是安全的,小于是危险状态,有可能会被清算。
vars.avgLtv = vars.totalCollateralInBaseCurrency != 0
? vars.avgLtv / vars.totalCollateralInBaseCurrency
: 0;
vars.avgLiquidationThreshold = vars.totalCollateralInBaseCurrency != 0
? vars.avgLiquidationThreshold / vars.totalCollateralInBaseCurrency
: 0;
在循环的过程中avgLtv 累加了每个资产乘以各自LTV后的金额;avgLiquidationThreshold 累加了每个资产乘以各自清算阈值后的金额。而这两个只应该是百分比;
- LTV : 贷款价值比,比如 75% 意味着价值 $100 的抵押品最多能借 $75
- 清算阈值: 触发清算的抵押品价值阈值,比如 80%,当抵押品价值跌到借款额的 80% 时触发清算
所以除以用户的总抵押金额,获取当前用户总资产的LTV和清算阈值。

817

被折叠的 条评论
为什么被折叠?



