-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathV2Calculations.sol
224 lines (200 loc) · 7.22 KB
/
V2Calculations.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
pragma solidity >=0.8.0 <0.9.0;
// SPDX-License-Identifier: MIT
// Libraries
import "./NumbersLib.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import { Bid } from "../TellerV2Storage.sol";
import { BokkyPooBahsDateTimeLibrary as BPBDTL } from "./DateTimeLib.sol";
enum PaymentType {
EMI,
Bullet
}
enum PaymentCycleType {
Seconds,
Monthly
}
library V2Calculations {
using NumbersLib for uint256;
/**
* @notice Returns the timestamp of the last payment made for a loan.
* @param _bid The loan bid struct to get the timestamp for.
*/
function lastRepaidTimestamp(Bid storage _bid)
internal
view
returns (uint32)
{
return
_bid.loanDetails.lastRepaidTimestamp == 0
? _bid.loanDetails.acceptedTimestamp
: _bid.loanDetails.lastRepaidTimestamp;
}
/**
* @notice Calculates the amount owed for a loan.
* @param _bid The loan bid struct to get the owed amount for.
* @param _timestamp The timestamp at which to get the owed amount at.
* @param _paymentCycleType The payment cycle type of the loan (Seconds or Monthly).
*/
function calculateAmountOwed(
Bid storage _bid,
uint256 _timestamp,
PaymentCycleType _paymentCycleType,
uint32 _paymentCycleDuration
)
public
view
returns (
uint256 owedPrincipal_,
uint256 duePrincipal_,
uint256 interest_
)
{
// Total principal left to pay
return
calculateAmountOwed(
_bid,
lastRepaidTimestamp(_bid),
_timestamp,
_paymentCycleType,
_paymentCycleDuration
);
}
function calculateAmountOwed(
Bid storage _bid,
uint256 _lastRepaidTimestamp,
uint256 _timestamp,
PaymentCycleType _paymentCycleType,
uint32 _paymentCycleDuration
)
public
view
returns (
uint256 owedPrincipal_,
uint256 duePrincipal_,
uint256 interest_
)
{
owedPrincipal_ =
_bid.loanDetails.principal -
_bid.loanDetails.totalRepaid.principal;
uint256 owedTime = _timestamp - uint256(_lastRepaidTimestamp);
{
uint256 daysInYear = _paymentCycleType == PaymentCycleType.Monthly
? 360 days
: 365 days;
uint256 interestOwedInAYear = owedPrincipal_.percent(_bid.terms.APR, 2);
interest_ = (interestOwedInAYear * owedTime) / daysInYear;
}
bool isLastPaymentCycle;
{
uint256 lastPaymentCycleDuration = _bid.loanDetails.loanDuration %
_paymentCycleDuration;
if (lastPaymentCycleDuration == 0) {
lastPaymentCycleDuration = _paymentCycleDuration;
}
uint256 endDate = uint256(_bid.loanDetails.acceptedTimestamp) +
uint256(_bid.loanDetails.loanDuration);
uint256 lastPaymentCycleStart = endDate -
uint256(lastPaymentCycleDuration);
isLastPaymentCycle =
uint256(_timestamp) > lastPaymentCycleStart ||
owedPrincipal_ + interest_ <= _bid.terms.paymentCycleAmount;
}
if (_bid.paymentType == PaymentType.Bullet) {
if (isLastPaymentCycle) {
duePrincipal_ = owedPrincipal_;
}
} else {
// Default to PaymentType.EMI
// Max payable amount in a cycle
// NOTE: the last cycle could have less than the calculated payment amount
//the amount owed for the cycle should never exceed the current payment cycle amount so we use min here
uint256 owedAmountForCycle = Math.min( ((_bid.terms.paymentCycleAmount * owedTime) ) /
_paymentCycleDuration , _bid.terms.paymentCycleAmount+interest_ ) ;
uint256 owedAmount = isLastPaymentCycle
? owedPrincipal_ + interest_
: owedAmountForCycle ;
duePrincipal_ = Math.min(owedAmount - interest_, owedPrincipal_);
}
}
/**
* @notice Calculates the amount owed for a loan for the next payment cycle.
* @param _type The payment type of the loan.
* @param _cycleType The cycle type set for the loan. (Seconds or Monthly)
* @param _principal The starting amount that is owed on the loan.
* @param _duration The length of the loan.
* @param _paymentCycle The length of the loan's payment cycle.
* @param _apr The annual percentage rate of the loan.
*/
function calculatePaymentCycleAmount(
PaymentType _type,
PaymentCycleType _cycleType,
uint256 _principal,
uint32 _duration,
uint32 _paymentCycle,
uint16 _apr
) public view returns (uint256) {
uint256 daysInYear = _cycleType == PaymentCycleType.Monthly
? 360 days
: 365 days;
if (_type == PaymentType.Bullet) {
return
_principal.percent(_apr).percent(
uint256(_paymentCycle).ratioOf(daysInYear, 10),
10
);
}
// Default to PaymentType.EMI
return
NumbersLib.pmt(
_principal,
_duration,
_paymentCycle,
_apr,
daysInYear
);
}
function calculateNextDueDate(
uint32 _acceptedTimestamp,
uint32 _paymentCycle,
uint32 _loanDuration,
uint32 _lastRepaidTimestamp,
PaymentCycleType _bidPaymentCycleType
) public view returns (uint32 dueDate_) {
// Calculate due date if payment cycle is set to monthly
if (_bidPaymentCycleType == PaymentCycleType.Monthly) {
// Calculate the cycle number the last repayment was made
uint256 lastPaymentCycle = BPBDTL.diffMonths(
_acceptedTimestamp,
_lastRepaidTimestamp
);
if (
BPBDTL.getDay(_lastRepaidTimestamp) >
BPBDTL.getDay(_acceptedTimestamp)
) {
lastPaymentCycle += 2;
} else {
lastPaymentCycle += 1;
}
dueDate_ = uint32(
BPBDTL.addMonths(_acceptedTimestamp, lastPaymentCycle)
);
} else if (_bidPaymentCycleType == PaymentCycleType.Seconds) {
// Start with the original due date being 1 payment cycle since bid was accepted
dueDate_ = _acceptedTimestamp + _paymentCycle;
// Calculate the cycle number the last repayment was made
uint32 delta = _lastRepaidTimestamp - _acceptedTimestamp;
if (delta > 0) {
uint32 repaymentCycle = uint32(
Math.ceilDiv(delta, _paymentCycle)
);
dueDate_ += (repaymentCycle * _paymentCycle);
}
}
uint32 endOfLoan = _acceptedTimestamp + _loanDuration;
//if we are in the last payment cycle, the next due date is the end of loan duration
if (dueDate_ > endOfLoan) {
dueDate_ = endOfLoan;
}
}
}