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