-
Notifications
You must be signed in to change notification settings - Fork 15
/
Compensate.sol
157 lines (135 loc) · 7 KB
/
Compensate.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {State} from "@src/SizeStorage.sol";
import {Math} from "@src/libraries/Math.sol";
import {AccountingLibrary} from "@src/libraries/AccountingLibrary.sol";
import {Errors} from "@src/libraries/Errors.sol";
import {Events} from "@src/libraries/Events.sol";
import {CreditPosition, DebtPosition, LoanLibrary, LoanStatus, RESERVED_ID} from "@src/libraries/LoanLibrary.sol";
import {RiskLibrary} from "@src/libraries/RiskLibrary.sol";
struct CompensateParams {
// The credit position ID with debt to repay
uint256 creditPositionWithDebtToRepayId;
// The credit position ID to compensate
// If RESERVED_ID, a new credit position will be created
uint256 creditPositionToCompensateId;
// The amount to compensate
// The maximum amount to compensate is the minimum of the credits
uint256 amount;
}
/// @title Compensate
/// @custom:security-contact [email protected]
/// @author Size (https://size.credit/)
/// @notice Contains the logic for compensating a credit position
library Compensate {
using AccountingLibrary for State;
using LoanLibrary for State;
using LoanLibrary for DebtPosition;
using LoanLibrary for CreditPosition;
using RiskLibrary for State;
/// @notice Validates the input parameters for compensating a credit position
/// @param state The state
/// @param params The input parameters for compensating a credit position
function validateCompensate(State storage state, CompensateParams calldata params) external view {
CreditPosition storage creditPositionWithDebtToRepay =
state.getCreditPosition(params.creditPositionWithDebtToRepayId);
DebtPosition storage debtPositionToRepay =
state.getDebtPositionByCreditPositionId(params.creditPositionWithDebtToRepayId);
uint256 amountToCompensate = Math.min(params.amount, creditPositionWithDebtToRepay.credit);
// validate creditPositionWithDebtToRepayId
if (state.getLoanStatus(params.creditPositionWithDebtToRepayId) != LoanStatus.ACTIVE) {
revert Errors.LOAN_NOT_ACTIVE(params.creditPositionWithDebtToRepayId);
}
// validate creditPositionToCompensateId
if (params.creditPositionToCompensateId == RESERVED_ID) {
uint256 tenor = debtPositionToRepay.dueDate - block.timestamp;
// validate tenor
if (tenor < state.riskConfig.minTenor || tenor > state.riskConfig.maxTenor) {
revert Errors.TENOR_OUT_OF_RANGE(tenor, state.riskConfig.minTenor, state.riskConfig.maxTenor);
}
} else {
CreditPosition storage creditPositionToCompensate =
state.getCreditPosition(params.creditPositionToCompensateId);
DebtPosition storage debtPositionToCompensate =
state.getDebtPositionByCreditPositionId(params.creditPositionToCompensateId);
if (!state.isCreditPositionTransferrable(params.creditPositionToCompensateId)) {
revert Errors.CREDIT_POSITION_NOT_TRANSFERRABLE(
params.creditPositionToCompensateId,
state.getLoanStatus(params.creditPositionToCompensateId),
state.collateralRatio(debtPositionToCompensate.borrower)
);
}
if (
debtPositionToRepay.dueDate
< state.getDebtPositionByCreditPositionId(params.creditPositionToCompensateId).dueDate
) {
revert Errors.DUE_DATE_NOT_COMPATIBLE(
params.creditPositionWithDebtToRepayId, params.creditPositionToCompensateId
);
}
if (creditPositionToCompensate.lender != debtPositionToRepay.borrower) {
revert Errors.INVALID_LENDER(creditPositionToCompensate.lender);
}
if (params.creditPositionToCompensateId == params.creditPositionWithDebtToRepayId) {
revert Errors.INVALID_CREDIT_POSITION_ID(params.creditPositionToCompensateId);
}
amountToCompensate = Math.min(amountToCompensate, creditPositionToCompensate.credit);
}
// validate msg.sender
if (msg.sender != debtPositionToRepay.borrower) {
revert Errors.COMPENSATOR_IS_NOT_BORROWER(msg.sender, debtPositionToRepay.borrower);
}
// validate amount
if (amountToCompensate == 0) {
revert Errors.NULL_AMOUNT();
}
}
/// @notice Executes the compensating of a credit position
/// @param state The state
/// @param params The input parameters for compensating a credit position
function executeCompensate(State storage state, CompensateParams calldata params) external {
emit Events.Compensate(
params.creditPositionWithDebtToRepayId, params.creditPositionToCompensateId, params.amount
);
CreditPosition storage creditPositionWithDebtToRepay =
state.getCreditPosition(params.creditPositionWithDebtToRepayId);
DebtPosition storage debtPositionToRepay =
state.getDebtPositionByCreditPositionId(params.creditPositionWithDebtToRepayId);
uint256 amountToCompensate = Math.min(params.amount, creditPositionWithDebtToRepay.credit);
CreditPosition memory creditPositionToCompensate;
if (params.creditPositionToCompensateId == RESERVED_ID) {
creditPositionToCompensate = state.createDebtAndCreditPositions({
lender: msg.sender,
borrower: msg.sender,
futureValue: amountToCompensate,
dueDate: debtPositionToRepay.dueDate
});
} else {
creditPositionToCompensate = state.getCreditPosition(params.creditPositionToCompensateId);
amountToCompensate = Math.min(amountToCompensate, creditPositionToCompensate.credit);
}
// debt and credit reduction
state.reduceDebtAndCredit(
creditPositionWithDebtToRepay.debtPositionId, params.creditPositionWithDebtToRepayId, amountToCompensate
);
uint256 exiterCreditRemaining = creditPositionToCompensate.credit - amountToCompensate;
// credit emission
state.createCreditPosition({
exitCreditPositionId: params.creditPositionToCompensateId == RESERVED_ID
? state.data.nextCreditPositionId - 1
: params.creditPositionToCompensateId,
lender: creditPositionWithDebtToRepay.lender,
credit: amountToCompensate
});
if (exiterCreditRemaining > 0) {
// charge the fragmentation fee in collateral tokens, capped by the user balance
uint256 fragmentationFeeInCollateral = Math.min(
state.debtTokenAmountToCollateralTokenAmount(state.feeConfig.fragmentationFee),
state.data.collateralToken.balanceOf(msg.sender)
);
state.data.collateralToken.transferFrom(
msg.sender, state.feeConfig.feeRecipient, fragmentationFeeInCollateral
);
}
}
}