-
Notifications
You must be signed in to change notification settings - Fork 16
/
Liquidate.sol
127 lines (103 loc) · 5.63 KB
/
Liquidate.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {Math} from "@src/libraries/Math.sol";
import {PERCENT} from "@src/libraries/Math.sol";
import {DebtPosition, LoanLibrary, LoanStatus} from "@src/libraries/LoanLibrary.sol";
import {AccountingLibrary} from "@src/libraries/AccountingLibrary.sol";
import {RiskLibrary} from "@src/libraries/RiskLibrary.sol";
import {State} from "@src/SizeStorage.sol";
import {Errors} from "@src/libraries/Errors.sol";
import {Events} from "@src/libraries/Events.sol";
struct LiquidateParams {
// The debt position ID to liquidate
uint256 debtPositionId;
// The minimum profit in collateral tokens expected by the liquidator
uint256 minimumCollateralProfit;
}
/// @title Liquidate
/// @custom:security-contact security@size.credit
/// @author Size (https://size.credit/)
/// @notice Contains the logic for liquidating a debt position
library Liquidate {
using LoanLibrary for DebtPosition;
using LoanLibrary for State;
using RiskLibrary for State;
using AccountingLibrary for State;
/// @notice Validates the input parameters for liquidating a debt position
/// @param state The state
function validateLiquidate(State storage state, LiquidateParams calldata params) external view {
DebtPosition storage debtPosition = state.getDebtPosition(params.debtPositionId);
// validate msg.sender
// N/A
// validate debtPositionId
if (!state.isDebtPositionLiquidatable(params.debtPositionId)) {
revert Errors.LOAN_NOT_LIQUIDATABLE(
params.debtPositionId,
state.collateralRatio(debtPosition.borrower),
state.getLoanStatus(params.debtPositionId)
);
}
// validate minimumCollateralProfit
// N/A
}
/// @notice Validates the minimum profit in collateral tokens expected by the liquidator
/// @param params The input parameters for liquidating a debt position
/// @param liquidatorProfitCollateralToken The profit in collateral tokens expected by the liquidator
function validateMinimumCollateralProfit(
State storage,
LiquidateParams calldata params,
uint256 liquidatorProfitCollateralToken
) external pure {
if (liquidatorProfitCollateralToken < params.minimumCollateralProfit) {
revert Errors.LIQUIDATE_PROFIT_BELOW_MINIMUM_COLLATERAL_PROFIT(
liquidatorProfitCollateralToken, params.minimumCollateralProfit
);
}
}
/// @notice Executes the liquidation of a debt position
/// @param state The state
/// @param params The input parameters for liquidating a debt position
/// @return liquidatorProfitCollateralToken The profit in collateral tokens expected by the liquidator
function executeLiquidate(State storage state, LiquidateParams calldata params)
external
returns (uint256 liquidatorProfitCollateralToken)
{
DebtPosition storage debtPosition = state.getDebtPosition(params.debtPositionId);
LoanStatus loanStatus = state.getLoanStatus(params.debtPositionId);
uint256 collateralRatio = state.collateralRatio(debtPosition.borrower);
emit Events.Liquidate(params.debtPositionId, params.minimumCollateralProfit, collateralRatio, loanStatus);
// if the loan is both underwater and overdue, the protocol fee related to underwater liquidations takes precedence
uint256 collateralProtocolPercent = state.isUserUnderwater(debtPosition.borrower)
? state.feeConfig.collateralProtocolPercent
: state.feeConfig.overdueCollateralProtocolPercent;
uint256 assignedCollateral = state.getDebtPositionAssignedCollateral(debtPosition);
uint256 debtInCollateralToken = state.debtTokenAmountToCollateralTokenAmount(debtPosition.futureValue);
uint256 protocolProfitCollateralToken = 0;
// profitable liquidation
if (assignedCollateral > debtInCollateralToken) {
uint256 liquidatorReward = Math.min(
assignedCollateral - debtInCollateralToken,
Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
);
liquidatorProfitCollateralToken = debtInCollateralToken + liquidatorReward;
// split the remaining collateral between the protocol and the borrower, capped by the crLiquidation
uint256 collateralRemainder = assignedCollateral - liquidatorProfitCollateralToken;
// cap the collateral remainder to the liquidation collateral ratio
// otherwise, the split for non-underwater overdue loans could be too much
uint256 collateralRemainderCap =
Math.mulDivDown(debtInCollateralToken, state.riskConfig.crLiquidation, PERCENT);
collateralRemainder = Math.min(collateralRemainder, collateralRemainderCap);
protocolProfitCollateralToken = Math.mulDivDown(collateralRemainder, collateralProtocolPercent, PERCENT);
} else {
// unprofitable liquidation
liquidatorProfitCollateralToken = assignedCollateral;
}
state.data.borrowAToken.transferFrom(msg.sender, address(this), debtPosition.futureValue);
state.data.collateralToken.transferFrom(debtPosition.borrower, msg.sender, liquidatorProfitCollateralToken);
state.data.collateralToken.transferFrom(
debtPosition.borrower, state.feeConfig.feeRecipient, protocolProfitCollateralToken
);
debtPosition.liquidityIndexAtRepayment = state.data.borrowAToken.liquidityIndex();
state.repayDebt(params.debtPositionId, debtPosition.futureValue);
}
}