-
Notifications
You must be signed in to change notification settings - Fork 15
/
RiskLibrary.sol
151 lines (136 loc) · 7.19 KB
/
RiskLibrary.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {State} from "@src/SizeStorage.sol";
import {Errors} from "@src/libraries/Errors.sol";
import {CreditPosition, DebtPosition, LoanLibrary, LoanStatus} from "@src/libraries/LoanLibrary.sol";
import {Math} from "@src/libraries/Math.sol";
/// @title RiskLibrary
/// @custom:security-contact [email protected]
/// @author Size (https://size.credit/)
library RiskLibrary {
using LoanLibrary for State;
/// @notice Validate the credit amount during an exit
/// @dev Reverts if the remaining credit is lower than the minimum credit
/// @param state The state
/// @param credit The remaining credit
function validateMinimumCredit(State storage state, uint256 credit) public view {
if (0 < credit && credit < state.riskConfig.minimumCreditBorrowAToken) {
revert Errors.CREDIT_LOWER_THAN_MINIMUM_CREDIT(credit, state.riskConfig.minimumCreditBorrowAToken);
}
}
/// @notice Validate the credit amount during an opening
/// @dev Reverts if the credit is lower than the minimum credit
/// @param state The state
/// @param credit The credit
function validateMinimumCreditOpening(State storage state, uint256 credit) public view {
if (credit < state.riskConfig.minimumCreditBorrowAToken) {
revert Errors.CREDIT_LOWER_THAN_MINIMUM_CREDIT_OPENING(credit, state.riskConfig.minimumCreditBorrowAToken);
}
}
/// @notice Validate the tenor of a loan
/// @dev Reverts if the tenor is out of range defined by minTenor and maxTenor
/// @param state The state
/// @param tenor The tenor
function validateTenor(State storage state, uint256 tenor) public view {
if (tenor < state.riskConfig.minTenor || tenor > state.riskConfig.maxTenor) {
revert Errors.TENOR_OUT_OF_RANGE(tenor, state.riskConfig.minTenor, state.riskConfig.maxTenor);
}
}
/// @notice Calculate the collateral ratio of an account
/// @dev The collateral ratio is the ratio of the collateral to the debt
/// If the debt is 0, the collateral ratio is type(uint256).max
/// @param state The state
/// @param account The account
/// @return The collateral ratio
function collateralRatio(State storage state, address account) public view returns (uint256) {
uint256 collateral = state.data.collateralToken.balanceOf(account);
uint256 debt = state.data.debtToken.balanceOf(account);
uint256 debtWad = Math.amountToWad(debt, state.data.underlyingBorrowToken.decimals());
uint256 price = state.oracle.priceFeed.getPrice();
if (debt != 0) {
return Math.mulDivDown(collateral, price, debtWad);
} else {
return type(uint256).max;
}
}
/// @notice Check if a credit position is self-liquidatable
/// @dev A credit position is self-liquidatable if the user is underwater and the loan is not REPAID (ie, ACTIVE or OVERDUE)
/// @param state The state
/// @param creditPositionId The credit position ID
/// @return True if the credit position is self-liquidatable, false otherwise
function isCreditPositionSelfLiquidatable(State storage state, uint256 creditPositionId)
public
view
returns (bool)
{
CreditPosition storage creditPosition = state.data.creditPositions[creditPositionId];
DebtPosition storage debtPosition = state.data.debtPositions[creditPosition.debtPositionId];
LoanStatus status = state.getLoanStatus(creditPositionId);
// Only CreditPositions can be self liquidated
return state.isCreditPositionId(creditPositionId)
&& (isUserUnderwater(state, debtPosition.borrower) && status != LoanStatus.REPAID);
}
/// @notice Check if a credit position is transferrable
/// @dev A credit position is transferrable if the loan is ACTIVE and the related borrower is not underwater
/// @param state The state
/// @param creditPositionId The credit position ID
/// @return True if the credit position is transferrable, false otherwise
function isCreditPositionTransferrable(State storage state, uint256 creditPositionId)
internal
view
returns (bool)
{
return state.getLoanStatus(creditPositionId) == LoanStatus.ACTIVE
&& !isUserUnderwater(state, state.getDebtPositionByCreditPositionId(creditPositionId).borrower);
}
/// @notice Check if a debt position is liquidatable
/// @dev A debt position is liquidatable if the user is underwater and the loan is not REPAID (ie, ACTIVE or OVERDUE) or
/// if the loan is OVERDUE.
/// @param state The state
/// @param debtPositionId The debt position ID
/// @return True if the debt position is liquidatable, false otherwise
function isDebtPositionLiquidatable(State storage state, uint256 debtPositionId) public view returns (bool) {
DebtPosition storage debtPosition = state.data.debtPositions[debtPositionId];
LoanStatus status = state.getLoanStatus(debtPositionId);
// only DebtPositions can be liquidated
return state.isDebtPositionId(debtPositionId)
// case 1: if the user is underwater, only ACTIVE/OVERDUE DebtPositions can be liquidated
&& (
(isUserUnderwater(state, debtPosition.borrower) && status != LoanStatus.REPAID)
// case 2: overdue loans can always be liquidated regardless of the user's CR
|| status == LoanStatus.OVERDUE
);
}
/// @notice Check if the user is underwater
/// @dev A user is underwater if the collateral ratio is below the liquidation threshold
/// @param state The state
/// @param account The account
function isUserUnderwater(State storage state, address account) public view returns (bool) {
return collateralRatio(state, account) < state.riskConfig.crLiquidation;
}
/// @notice Validate that the user is not underwater
/// @dev Reverts if the user is underwater
/// @param state The state
/// @param account The account
function validateUserIsNotUnderwater(State storage state, address account) external view {
if (isUserUnderwater(state, account)) {
revert Errors.USER_IS_UNDERWATER(account, collateralRatio(state, account));
}
}
/// @notice Validate that the user is not below the opening limit borrow CR
/// @dev Reverts if the user is below the opening limit borrow CR
/// The user can set a custom opening limit borrow CR using SetUserConfiguration
/// If the user has not set a custom opening limit borrow CR, the default is the global opening limit borrow CR
/// @param state The state
function validateUserIsNotBelowOpeningLimitBorrowCR(State storage state, address account) external view {
uint256 openingLimitBorrowCR = Math.max(
state.riskConfig.crOpening,
state.data.users[account].openingLimitBorrowCR // 0 by default, or user-defined if SetUserConfiguration has been used
);
if (collateralRatio(state, account) < openingLimitBorrowCR) {
revert Errors.CR_BELOW_OPENING_LIMIT_BORROW_CR(
account, collateralRatio(state, account), openingLimitBorrowCR
);
}
}
}