Branch data Line data Source code
1 : : // SPDX-License-Identifier: BUSL-1.1
2 : : pragma solidity ^0.8.10;
3 : :
4 : : import {IERC20} from '../../../dependencies/openzeppelin/contracts//IERC20.sol';
5 : : import {GPv2SafeERC20} from '../../../dependencies/gnosis/contracts/GPv2SafeERC20.sol';
6 : : import {PercentageMath} from '../../libraries/math/PercentageMath.sol';
7 : : import {WadRayMath} from '../../libraries/math/WadRayMath.sol';
8 : : import {DataTypes} from '../../libraries/types/DataTypes.sol';
9 : : import {ReserveLogic} from './ReserveLogic.sol';
10 : : import {ValidationLogic} from './ValidationLogic.sol';
11 : : import {GenericLogic} from './GenericLogic.sol';
12 : : import {IsolationModeLogic} from './IsolationModeLogic.sol';
13 : : import {EModeLogic} from './EModeLogic.sol';
14 : : import {UserConfiguration} from '../../libraries/configuration/UserConfiguration.sol';
15 : : import {ReserveConfiguration} from '../../libraries/configuration/ReserveConfiguration.sol';
16 : : import {EModeConfiguration} from '../../libraries/configuration/EModeConfiguration.sol';
17 : : import {IAToken} from '../../../interfaces/IAToken.sol';
18 : : import {IVariableDebtToken} from '../../../interfaces/IVariableDebtToken.sol';
19 : : import {IPriceOracleGetter} from '../../../interfaces/IPriceOracleGetter.sol';
20 : :
21 : : /**
22 : : * @title LiquidationLogic library
23 : : * @author Aave
24 : : * @notice Implements actions involving management of collateral in the protocol, the main one being the liquidations
25 : : */
26 : : library LiquidationLogic {
27 : : using WadRayMath for uint256;
28 : : using PercentageMath for uint256;
29 : : using ReserveLogic for DataTypes.ReserveCache;
30 : : using ReserveLogic for DataTypes.ReserveData;
31 : : using UserConfiguration for DataTypes.UserConfigurationMap;
32 : : using ReserveConfiguration for DataTypes.ReserveConfigurationMap;
33 : : using GPv2SafeERC20 for IERC20;
34 : :
35 : : // See `IPool` for descriptions
36 : : event ReserveUsedAsCollateralEnabled(address indexed reserve, address indexed user);
37 : : event ReserveUsedAsCollateralDisabled(address indexed reserve, address indexed user);
38 : : event LiquidationCall(
39 : : address indexed collateralAsset,
40 : : address indexed debtAsset,
41 : : address indexed user,
42 : : uint256 debtToCover,
43 : : uint256 liquidatedCollateralAmount,
44 : : address liquidator,
45 : : bool receiveAToken
46 : : );
47 : :
48 : : /**
49 : : * @dev Default percentage of borrower's debt to be repaid in a liquidation.
50 : : * @dev Percentage applied when the users health factor is above `CLOSE_FACTOR_HF_THRESHOLD`
51 : : * Expressed in bps, a value of 0.5e4 results in 50.00%
52 : : */
53 : : uint256 internal constant DEFAULT_LIQUIDATION_CLOSE_FACTOR = 0.5e4;
54 : :
55 : : /**
56 : : * @dev Maximum percentage of borrower's debt to be repaid in a liquidation
57 : : * @dev Percentage applied when the users health factor is below `CLOSE_FACTOR_HF_THRESHOLD`
58 : : * Expressed in bps, a value of 1e4 results in 100.00%
59 : : */
60 : : uint256 public constant MAX_LIQUIDATION_CLOSE_FACTOR = 1e4;
61 : :
62 : : /**
63 : : * @dev This constant represents below which health factor value it is possible to liquidate
64 : : * an amount of debt corresponding to `MAX_LIQUIDATION_CLOSE_FACTOR`.
65 : : * A value of 0.95e18 results in 0.95
66 : : */
67 : : uint256 public constant CLOSE_FACTOR_HF_THRESHOLD = 0.95e18;
68 : :
69 : : struct LiquidationCallLocalVars {
70 : : uint256 userCollateralBalance;
71 : : uint256 userTotalDebt;
72 : : uint256 actualDebtToLiquidate;
73 : : uint256 actualCollateralToLiquidate;
74 : : uint256 liquidationBonus;
75 : : uint256 healthFactor;
76 : : uint256 liquidationProtocolFeeAmount;
77 : : IAToken collateralAToken;
78 : : DataTypes.ReserveCache debtReserveCache;
79 : : }
80 : :
81 : : /**
82 : : * @notice Function to liquidate a position if its Health Factor drops below 1. The caller (liquidator)
83 : : * covers `debtToCover` amount of debt of the user getting liquidated, and receives
84 : : * a proportional amount of the `collateralAsset` plus a bonus to cover market risk
85 : : * @dev Emits the `LiquidationCall()` event
86 : : * @param reservesData The state of all the reserves
87 : : * @param reservesList The addresses of all the active reserves
88 : : * @param usersConfig The users configuration mapping that track the supplied/borrowed assets
89 : : * @param eModeCategories The configuration of all the efficiency mode categories
90 : : * @param params The additional parameters needed to execute the liquidation function
91 : : */
92 : : function executeLiquidationCall(
93 : : mapping(address => DataTypes.ReserveData) storage reservesData,
94 : : mapping(uint256 => address) storage reservesList,
95 : : mapping(address => DataTypes.UserConfigurationMap) storage usersConfig,
96 : : mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
97 : : DataTypes.ExecuteLiquidationCallParams memory params
98 : : ) external {
99 : 14501 : LiquidationCallLocalVars memory vars;
100 : :
101 : 14501 : DataTypes.ReserveData storage collateralReserve = reservesData[params.collateralAsset];
102 : 14501 : DataTypes.ReserveData storage debtReserve = reservesData[params.debtAsset];
103 : 14501 : DataTypes.UserConfigurationMap storage userConfig = usersConfig[params.user];
104 : 14501 : vars.debtReserveCache = debtReserve.cache();
105 : 14501 : debtReserve.updateState(vars.debtReserveCache);
106 : :
107 : 14501 : (, , , , vars.healthFactor, ) = GenericLogic.calculateUserAccountData(
108 : : reservesData,
109 : : reservesList,
110 : : eModeCategories,
111 : : DataTypes.CalculateUserAccountDataParams({
112 : : userConfig: userConfig,
113 : : reservesCount: params.reservesCount,
114 : : user: params.user,
115 : : oracle: params.priceOracle,
116 : : userEModeCategory: params.userEModeCategory
117 : : })
118 : : );
119 : :
120 : 14501 : (vars.userTotalDebt, vars.actualDebtToLiquidate) = _calculateDebt(
121 : : vars.debtReserveCache,
122 : : params,
123 : : vars.healthFactor
124 : : );
125 : :
126 : 14501 : ValidationLogic.validateLiquidationCall(
127 : : userConfig,
128 : : collateralReserve,
129 : : debtReserve,
130 : : DataTypes.ValidateLiquidationCallParams({
131 : : debtReserveCache: vars.debtReserveCache,
132 : : totalDebt: vars.userTotalDebt,
133 : : healthFactor: vars.healthFactor,
134 : : priceOracleSentinel: params.priceOracleSentinel
135 : : })
136 : : );
137 : :
138 : 8011 : vars.collateralAToken = IAToken(collateralReserve.aTokenAddress);
139 : : if (
140 : 8011 : params.userEModeCategory != 0 &&
141 : 1001 : EModeConfiguration.isReserveEnabledOnBitmap(
142 : : eModeCategories[params.userEModeCategory].collateralBitmap,
143 : : collateralReserve.id
144 : : )
145 : : ) {
146 : 1001 : vars.liquidationBonus = eModeCategories[params.userEModeCategory].liquidationBonus;
147 : : } else {
148 : 7010 : vars.liquidationBonus = collateralReserve.configuration.getLiquidationBonus();
149 : : }
150 : :
151 : 8011 : vars.userCollateralBalance = vars.collateralAToken.balanceOf(params.user);
152 : :
153 : 8011 : (
154 : : vars.actualCollateralToLiquidate,
155 : : vars.actualDebtToLiquidate,
156 : : vars.liquidationProtocolFeeAmount
157 : : ) = _calculateAvailableCollateralToLiquidate(
158 : : collateralReserve,
159 : : vars.debtReserveCache,
160 : : params.collateralAsset,
161 : : params.debtAsset,
162 : : vars.actualDebtToLiquidate,
163 : : vars.userCollateralBalance,
164 : : vars.liquidationBonus,
165 : : IPriceOracleGetter(params.priceOracle)
166 : : );
167 : :
168 : 8011 : if (vars.userTotalDebt == vars.actualDebtToLiquidate) {
169 : 2 : userConfig.setBorrowing(debtReserve.id, false);
170 : : }
171 : :
172 : : // If the collateral being liquidated is equal to the user balance,
173 : : // we set the currency as not being used as collateral anymore
174 : : if (
175 : 8011 : vars.actualCollateralToLiquidate + vars.liquidationProtocolFeeAmount ==
176 : : vars.userCollateralBalance
177 : : ) {
178 : 1006 : userConfig.setUsingAsCollateral(collateralReserve.id, false);
179 : 1006 : emit ReserveUsedAsCollateralDisabled(params.collateralAsset, params.user);
180 : : }
181 : :
182 : 8011 : _burnDebtTokens(params, vars);
183 : :
184 : 8011 : debtReserve.updateInterestRatesAndVirtualBalance(
185 : : vars.debtReserveCache,
186 : : params.debtAsset,
187 : : vars.actualDebtToLiquidate,
188 : : 0
189 : : );
190 : :
191 : 8011 : IsolationModeLogic.updateIsolatedDebtIfIsolated(
192 : : reservesData,
193 : : reservesList,
194 : : userConfig,
195 : : vars.debtReserveCache,
196 : : vars.actualDebtToLiquidate
197 : : );
198 : :
199 : 8011 : if (params.receiveAToken) {
200 : 3 : _liquidateATokens(reservesData, reservesList, usersConfig, collateralReserve, params, vars);
201 : : } else {
202 : 8008 : _burnCollateralATokens(collateralReserve, params, vars);
203 : : }
204 : :
205 : : // Transfer fee to treasury if it is non-zero
206 : 8011 : if (vars.liquidationProtocolFeeAmount != 0) {
207 : 8010 : uint256 liquidityIndex = collateralReserve.getNormalizedIncome();
208 : 8010 : uint256 scaledDownLiquidationProtocolFee = vars.liquidationProtocolFeeAmount.rayDiv(
209 : : liquidityIndex
210 : : );
211 : 8010 : uint256 scaledDownUserBalance = vars.collateralAToken.scaledBalanceOf(params.user);
212 : : // To avoid trying to send more aTokens than available on balance, due to 1 wei imprecision
213 : 8010 : if (scaledDownLiquidationProtocolFee > scaledDownUserBalance) {
214 : 0 : vars.liquidationProtocolFeeAmount = scaledDownUserBalance.rayMul(liquidityIndex);
215 : : }
216 : 8010 : vars.collateralAToken.transferOnLiquidation(
217 : : params.user,
218 : : vars.collateralAToken.RESERVE_TREASURY_ADDRESS(),
219 : : vars.liquidationProtocolFeeAmount
220 : : );
221 : : }
222 : :
223 : : // Transfers the debt asset being repaid to the aToken, where the liquidity is kept
224 : 8011 : IERC20(params.debtAsset).safeTransferFrom(
225 : : msg.sender,
226 : : vars.debtReserveCache.aTokenAddress,
227 : : vars.actualDebtToLiquidate
228 : : );
229 : :
230 : 8011 : IAToken(vars.debtReserveCache.aTokenAddress).handleRepayment(
231 : : msg.sender,
232 : : params.user,
233 : : vars.actualDebtToLiquidate
234 : : );
235 : :
236 : 8011 : emit LiquidationCall(
237 : : params.collateralAsset,
238 : : params.debtAsset,
239 : : params.user,
240 : : vars.actualDebtToLiquidate,
241 : : vars.actualCollateralToLiquidate,
242 : : msg.sender,
243 : : params.receiveAToken
244 : : );
245 : : }
246 : :
247 : : /**
248 : : * @notice Burns the collateral aTokens and transfers the underlying to the liquidator.
249 : : * @dev The function also updates the state and the interest rate of the collateral reserve.
250 : : * @param collateralReserve The data of the collateral reserve
251 : : * @param params The additional parameters needed to execute the liquidation function
252 : : * @param vars The executeLiquidationCall() function local vars
253 : : */
254 : : function _burnCollateralATokens(
255 : : DataTypes.ReserveData storage collateralReserve,
256 : : DataTypes.ExecuteLiquidationCallParams memory params,
257 : : LiquidationCallLocalVars memory vars
258 : : ) internal {
259 : 8008 : DataTypes.ReserveCache memory collateralReserveCache = collateralReserve.cache();
260 : 8008 : collateralReserve.updateState(collateralReserveCache);
261 : 8008 : collateralReserve.updateInterestRatesAndVirtualBalance(
262 : : collateralReserveCache,
263 : : params.collateralAsset,
264 : : 0,
265 : : vars.actualCollateralToLiquidate
266 : : );
267 : :
268 : : // Burn the equivalent amount of aToken, sending the underlying to the liquidator
269 : 8008 : vars.collateralAToken.burn(
270 : : params.user,
271 : : msg.sender,
272 : : vars.actualCollateralToLiquidate,
273 : : collateralReserveCache.nextLiquidityIndex
274 : : );
275 : : }
276 : :
277 : : /**
278 : : * @notice Liquidates the user aTokens by transferring them to the liquidator.
279 : : * @dev The function also checks the state of the liquidator and activates the aToken as collateral
280 : : * as in standard transfers if the isolation mode constraints are respected.
281 : : * @param reservesData The state of all the reserves
282 : : * @param reservesList The addresses of all the active reserves
283 : : * @param usersConfig The users configuration mapping that track the supplied/borrowed assets
284 : : * @param collateralReserve The data of the collateral reserve
285 : : * @param params The additional parameters needed to execute the liquidation function
286 : : * @param vars The executeLiquidationCall() function local vars
287 : : */
288 : : function _liquidateATokens(
289 : : mapping(address => DataTypes.ReserveData) storage reservesData,
290 : : mapping(uint256 => address) storage reservesList,
291 : : mapping(address => DataTypes.UserConfigurationMap) storage usersConfig,
292 : : DataTypes.ReserveData storage collateralReserve,
293 : : DataTypes.ExecuteLiquidationCallParams memory params,
294 : : LiquidationCallLocalVars memory vars
295 : : ) internal {
296 : 3 : uint256 liquidatorPreviousATokenBalance = IERC20(vars.collateralAToken).balanceOf(msg.sender);
297 : 3 : vars.collateralAToken.transferOnLiquidation(
298 : : params.user,
299 : : msg.sender,
300 : : vars.actualCollateralToLiquidate
301 : : );
302 : :
303 : 3 : if (liquidatorPreviousATokenBalance == 0) {
304 : 3 : DataTypes.UserConfigurationMap storage liquidatorConfig = usersConfig[msg.sender];
305 : : if (
306 : 3 : ValidationLogic.validateAutomaticUseAsCollateral(
307 : : reservesData,
308 : : reservesList,
309 : : liquidatorConfig,
310 : : collateralReserve.configuration,
311 : : collateralReserve.aTokenAddress
312 : : )
313 : : ) {
314 : 3 : liquidatorConfig.setUsingAsCollateral(collateralReserve.id, true);
315 : 3 : emit ReserveUsedAsCollateralEnabled(params.collateralAsset, msg.sender);
316 : : }
317 : : }
318 : : }
319 : :
320 : : /**
321 : : * @notice Burns the debt tokens of the user up to the amount being repaid by the liquidator.
322 : : * @dev The function alters the `debtReserveCache` state in `vars` to update the debt related data.
323 : : * @param params The additional parameters needed to execute the liquidation function
324 : : * @param vars the executeLiquidationCall() function local vars
325 : : */
326 : : function _burnDebtTokens(
327 : : DataTypes.ExecuteLiquidationCallParams memory params,
328 : : LiquidationCallLocalVars memory vars
329 : : ) internal {
330 : 8011 : vars.debtReserveCache.nextScaledVariableDebt = IVariableDebtToken(
331 : : vars.debtReserveCache.variableDebtTokenAddress
332 : : ).burn(params.user, vars.actualDebtToLiquidate, vars.debtReserveCache.nextVariableBorrowIndex);
333 : : }
334 : :
335 : : /**
336 : : * @notice Calculates the total debt of the user and the actual amount to liquidate depending on the health factor
337 : : * and corresponding close factor.
338 : : * @dev If the Health Factor is below CLOSE_FACTOR_HF_THRESHOLD, the close factor is increased to MAX_LIQUIDATION_CLOSE_FACTOR
339 : : * @param debtReserveCache The reserve cache data object of the debt reserve
340 : : * @param params The additional parameters needed to execute the liquidation function
341 : : * @param healthFactor The health factor of the position
342 : : * @return The total debt of the user
343 : : * @return The actual debt to liquidate as a function of the closeFactor
344 : : */
345 : : function _calculateDebt(
346 : : DataTypes.ReserveCache memory debtReserveCache,
347 : : DataTypes.ExecuteLiquidationCallParams memory params,
348 : : uint256 healthFactor
349 : : ) internal view returns (uint256, uint256) {
350 : 14501 : uint256 userVariableDebt = IERC20(debtReserveCache.variableDebtTokenAddress).balanceOf(
351 : : params.user
352 : : );
353 : :
354 : 14501 : uint256 closeFactor = healthFactor > CLOSE_FACTOR_HF_THRESHOLD
355 : : ? DEFAULT_LIQUIDATION_CLOSE_FACTOR
356 : : : MAX_LIQUIDATION_CLOSE_FACTOR;
357 : :
358 : 14501 : uint256 maxLiquidatableDebt = userVariableDebt.percentMul(closeFactor);
359 : :
360 : 14501 : uint256 actualDebtToLiquidate = params.debtToCover > maxLiquidatableDebt
361 : : ? maxLiquidatableDebt
362 : : : params.debtToCover;
363 : :
364 : 14501 : return (userVariableDebt, actualDebtToLiquidate);
365 : : }
366 : :
367 : : struct AvailableCollateralToLiquidateLocalVars {
368 : : uint256 collateralPrice;
369 : : uint256 debtAssetPrice;
370 : : uint256 maxCollateralToLiquidate;
371 : : uint256 baseCollateral;
372 : : uint256 bonusCollateral;
373 : : uint256 debtAssetDecimals;
374 : : uint256 collateralDecimals;
375 : : uint256 collateralAssetUnit;
376 : : uint256 debtAssetUnit;
377 : : uint256 collateralAmount;
378 : : uint256 debtAmountNeeded;
379 : : uint256 liquidationProtocolFeePercentage;
380 : : uint256 liquidationProtocolFee;
381 : : }
382 : :
383 : : /**
384 : : * @notice Calculates how much of a specific collateral can be liquidated, given
385 : : * a certain amount of debt asset.
386 : : * @dev This function needs to be called after all the checks to validate the liquidation have been performed,
387 : : * otherwise it might fail.
388 : : * @param collateralReserve The data of the collateral reserve
389 : : * @param debtReserveCache The cached data of the debt reserve
390 : : * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
391 : : * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
392 : : * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
393 : : * @param userCollateralBalance The collateral balance for the specific `collateralAsset` of the user being liquidated
394 : : * @param liquidationBonus The collateral bonus percentage to receive as result of the liquidation
395 : : * @return The maximum amount that is possible to liquidate given all the liquidation constraints (user balance, close factor)
396 : : * @return The amount to repay with the liquidation
397 : : * @return The fee taken from the liquidation bonus amount to be paid to the protocol
398 : : */
399 : : function _calculateAvailableCollateralToLiquidate(
400 : : DataTypes.ReserveData storage collateralReserve,
401 : : DataTypes.ReserveCache memory debtReserveCache,
402 : : address collateralAsset,
403 : : address debtAsset,
404 : : uint256 debtToCover,
405 : : uint256 userCollateralBalance,
406 : : uint256 liquidationBonus,
407 : : IPriceOracleGetter oracle
408 : : ) internal view returns (uint256, uint256, uint256) {
409 : 12021 : AvailableCollateralToLiquidateLocalVars memory vars;
410 : :
411 : 12021 : vars.collateralPrice = oracle.getAssetPrice(collateralAsset);
412 : 12021 : vars.debtAssetPrice = oracle.getAssetPrice(debtAsset);
413 : :
414 : 12021 : vars.collateralDecimals = collateralReserve.configuration.getDecimals();
415 : 12021 : vars.debtAssetDecimals = debtReserveCache.reserveConfiguration.getDecimals();
416 : :
417 : : unchecked {
418 : 12021 : vars.collateralAssetUnit = 10 ** vars.collateralDecimals;
419 : 12021 : vars.debtAssetUnit = 10 ** vars.debtAssetDecimals;
420 : : }
421 : :
422 : 12021 : vars.liquidationProtocolFeePercentage = collateralReserve
423 : : .configuration
424 : : .getLiquidationProtocolFee();
425 : :
426 : : // This is the base collateral to liquidate based on the given debt to cover
427 : 12021 : vars.baseCollateral =
428 : : ((vars.debtAssetPrice * debtToCover * vars.collateralAssetUnit)) /
429 : : (vars.collateralPrice * vars.debtAssetUnit);
430 : :
431 : 12021 : vars.maxCollateralToLiquidate = vars.baseCollateral.percentMul(liquidationBonus);
432 : :
433 : 12021 : if (vars.maxCollateralToLiquidate > userCollateralBalance) {
434 : 1011 : vars.collateralAmount = userCollateralBalance;
435 : 1011 : vars.debtAmountNeeded = ((vars.collateralPrice * vars.collateralAmount * vars.debtAssetUnit) /
436 : : (vars.debtAssetPrice * vars.collateralAssetUnit)).percentDiv(liquidationBonus);
437 : : } else {
438 : 11010 : vars.collateralAmount = vars.maxCollateralToLiquidate;
439 : 11010 : vars.debtAmountNeeded = debtToCover;
440 : : }
441 : :
442 : 12021 : if (vars.liquidationProtocolFeePercentage != 0) {
443 : 12019 : vars.bonusCollateral =
444 : : vars.collateralAmount -
445 : : vars.collateralAmount.percentDiv(liquidationBonus);
446 : :
447 : 12019 : vars.liquidationProtocolFee = vars.bonusCollateral.percentMul(
448 : : vars.liquidationProtocolFeePercentage
449 : : );
450 : :
451 : 12019 : return (
452 : : vars.collateralAmount - vars.liquidationProtocolFee,
453 : : vars.debtAmountNeeded,
454 : : vars.liquidationProtocolFee
455 : : );
456 : : } else {
457 : 2 : return (vars.collateralAmount, vars.debtAmountNeeded, 0);
458 : : }
459 : : }
460 : : }
|