@sovryn-zero/lib-base 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +17 -0
- package/.mocharc.yml +1 -0
- package/LICENSE +905 -0
- package/README.md +23 -0
- package/api-extractor.json +4 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/src/Decimal.d.ts +89 -0
- package/dist/src/Decimal.d.ts.map +1 -0
- package/dist/src/Decimal.js +361 -0
- package/dist/src/Decimal.js.map +1 -0
- package/dist/src/Fees.d.ts +82 -0
- package/dist/src/Fees.d.ts.map +1 -0
- package/dist/src/Fees.js +123 -0
- package/dist/src/Fees.js.map +1 -0
- package/dist/src/LiquityStore.d.ts +209 -0
- package/dist/src/LiquityStore.d.ts.map +1 -0
- package/dist/src/LiquityStore.js +209 -0
- package/dist/src/LiquityStore.js.map +1 -0
- package/dist/src/ObservableLiquity.d.ts +15 -0
- package/dist/src/ObservableLiquity.d.ts.map +1 -0
- package/dist/src/ObservableLiquity.js +3 -0
- package/dist/src/ObservableLiquity.js.map +1 -0
- package/dist/src/PopulatableLiquity.d.ts +125 -0
- package/dist/src/PopulatableLiquity.d.ts.map +1 -0
- package/dist/src/PopulatableLiquity.js +3 -0
- package/dist/src/PopulatableLiquity.js.map +1 -0
- package/dist/src/ReadableLiquity.d.ts +156 -0
- package/dist/src/ReadableLiquity.d.ts.map +1 -0
- package/dist/src/ReadableLiquity.js +3 -0
- package/dist/src/ReadableLiquity.js.map +1 -0
- package/dist/src/SendableLiquity.d.ts +156 -0
- package/dist/src/SendableLiquity.d.ts.map +1 -0
- package/dist/src/SendableLiquity.js +20 -0
- package/dist/src/SendableLiquity.js.map +1 -0
- package/dist/src/StabilityDeposit.d.ts +59 -0
- package/dist/src/StabilityDeposit.d.ts.map +1 -0
- package/dist/src/StabilityDeposit.js +80 -0
- package/dist/src/StabilityDeposit.js.map +1 -0
- package/dist/src/TransactableLiquity.d.ts +414 -0
- package/dist/src/TransactableLiquity.d.ts.map +1 -0
- package/dist/src/TransactableLiquity.js +18 -0
- package/dist/src/TransactableLiquity.js.map +1 -0
- package/dist/src/Trove.d.ts +367 -0
- package/dist/src/Trove.d.ts.map +1 -0
- package/dist/src/Trove.js +423 -0
- package/dist/src/Trove.js.map +1 -0
- package/dist/src/ZEROStake.d.ts +52 -0
- package/dist/src/ZEROStake.d.ts.map +1 -0
- package/dist/src/ZEROStake.js +74 -0
- package/dist/src/ZEROStake.js.map +1 -0
- package/dist/src/_CachedReadableLiquity.d.ts +55 -0
- package/dist/src/_CachedReadableLiquity.d.ts.map +1 -0
- package/dist/src/_CachedReadableLiquity.js +93 -0
- package/dist/src/_CachedReadableLiquity.js.map +1 -0
- package/dist/src/constants.d.ts +61 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +64 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/etc/lib-base.api.md +788 -0
- package/index.ts +13 -0
- package/package.json +52 -0
- package/src/Decimal.ts +456 -0
- package/src/Fees.ts +160 -0
- package/src/LiquityStore.ts +563 -0
- package/src/ObservableLiquity.ts +32 -0
- package/src/PopulatableLiquity.ts +280 -0
- package/src/ReadableLiquity.ts +175 -0
- package/src/SendableLiquity.ts +251 -0
- package/src/StabilityDeposit.ts +126 -0
- package/src/TransactableLiquity.ts +471 -0
- package/src/Trove.ts +824 -0
- package/src/ZEROStake.ts +99 -0
- package/src/_CachedReadableLiquity.ts +186 -0
- package/src/constants.ts +68 -0
- package/test/Decimal.test.ts +212 -0
- package/test/StabilityDeposit.test.ts +30 -0
- package/test/Trove.test.ts +143 -0
- package/test/ZEROStake.test.ts +24 -0
- package/tsconfig.dist.json +8 -0
- package/tsconfig.json +5 -0
package/src/Trove.ts
ADDED
@@ -0,0 +1,824 @@
|
|
1
|
+
import assert from "assert";
|
2
|
+
|
3
|
+
import { Decimal, Decimalish } from "./Decimal";
|
4
|
+
|
5
|
+
import {
|
6
|
+
MINIMUM_COLLATERAL_RATIO,
|
7
|
+
CRITICAL_COLLATERAL_RATIO,
|
8
|
+
ZUSD_LIQUIDATION_RESERVE,
|
9
|
+
MINIMUM_BORROWING_RATE
|
10
|
+
} from "./constants";
|
11
|
+
|
12
|
+
/** @internal */ export type _CollateralDeposit<T> = { depositCollateral: T };
|
13
|
+
/** @internal */ export type _CollateralWithdrawal<T> = { withdrawCollateral: T };
|
14
|
+
/** @internal */ export type _ZUSDBorrowing<T> = { borrowZUSD: T };
|
15
|
+
/** @internal */ export type _ZUSDRepayment<T> = { repayZUSD: T };
|
16
|
+
|
17
|
+
/** @internal */ export type _NoCollateralDeposit = Partial<_CollateralDeposit<undefined>>;
|
18
|
+
/** @internal */ export type _NoCollateralWithdrawal = Partial<_CollateralWithdrawal<undefined>>;
|
19
|
+
/** @internal */ export type _NoZUSDBorrowing = Partial<_ZUSDBorrowing<undefined>>;
|
20
|
+
/** @internal */ export type _NoZUSDRepayment = Partial<_ZUSDRepayment<undefined>>;
|
21
|
+
|
22
|
+
/** @internal */
|
23
|
+
export type _CollateralChange<T> =
|
24
|
+
| (_CollateralDeposit<T> & _NoCollateralWithdrawal)
|
25
|
+
| (_CollateralWithdrawal<T> & _NoCollateralDeposit);
|
26
|
+
|
27
|
+
/** @internal */
|
28
|
+
export type _NoCollateralChange = _NoCollateralDeposit & _NoCollateralWithdrawal;
|
29
|
+
|
30
|
+
/** @internal */
|
31
|
+
export type _DebtChange<T> =
|
32
|
+
| (_ZUSDBorrowing<T> & _NoZUSDRepayment)
|
33
|
+
| (_ZUSDRepayment<T> & _NoZUSDBorrowing);
|
34
|
+
|
35
|
+
/** @internal */
|
36
|
+
export type _NoDebtChange = _NoZUSDBorrowing & _NoZUSDRepayment;
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Parameters of an {@link TransactableLiquity.openTrove | openTrove()} transaction.
|
40
|
+
*
|
41
|
+
* @remarks
|
42
|
+
* The type parameter `T` specifies the allowed value type(s) of the particular `TroveCreationParams`
|
43
|
+
* object's properties.
|
44
|
+
*
|
45
|
+
* <h2>Properties</h2>
|
46
|
+
*
|
47
|
+
* <table>
|
48
|
+
*
|
49
|
+
* <tr>
|
50
|
+
* <th> Property </th>
|
51
|
+
* <th> Type </th>
|
52
|
+
* <th> Description </th>
|
53
|
+
* </tr>
|
54
|
+
*
|
55
|
+
* <tr>
|
56
|
+
* <td> depositCollateral </td>
|
57
|
+
* <td> T </td>
|
58
|
+
* <td> The amount of collateral that's deposited. </td>
|
59
|
+
* </tr>
|
60
|
+
*
|
61
|
+
* <tr>
|
62
|
+
* <td> borrowZUSD </td>
|
63
|
+
* <td> T </td>
|
64
|
+
* <td> The amount of ZUSD that's borrowed. </td>
|
65
|
+
* </tr>
|
66
|
+
*
|
67
|
+
* </table>
|
68
|
+
*
|
69
|
+
* @public
|
70
|
+
*/
|
71
|
+
export type TroveCreationParams<T = unknown> = _CollateralDeposit<T> &
|
72
|
+
_NoCollateralWithdrawal &
|
73
|
+
_ZUSDBorrowing<T> &
|
74
|
+
_NoZUSDRepayment;
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Parameters of a {@link TransactableLiquity.closeTrove | closeTrove()} transaction.
|
78
|
+
*
|
79
|
+
* @remarks
|
80
|
+
* The type parameter `T` specifies the allowed value type(s) of the particular `TroveClosureParams`
|
81
|
+
* object's properties.
|
82
|
+
*
|
83
|
+
* <h2>Properties</h2>
|
84
|
+
*
|
85
|
+
* <table>
|
86
|
+
*
|
87
|
+
* <tr>
|
88
|
+
* <th> Property </th>
|
89
|
+
* <th> Type </th>
|
90
|
+
* <th> Description </th>
|
91
|
+
* </tr>
|
92
|
+
*
|
93
|
+
* <tr>
|
94
|
+
* <td> withdrawCollateral </td>
|
95
|
+
* <td> T </td>
|
96
|
+
* <td> The amount of collateral that's withdrawn. </td>
|
97
|
+
* </tr>
|
98
|
+
*
|
99
|
+
* <tr>
|
100
|
+
* <td> repayZUSD? </td>
|
101
|
+
* <td> T </td>
|
102
|
+
* <td> <i>(Optional)</i> The amount of ZUSD that's repaid. </td>
|
103
|
+
* </tr>
|
104
|
+
*
|
105
|
+
* </table>
|
106
|
+
*
|
107
|
+
* @public
|
108
|
+
*/
|
109
|
+
export type TroveClosureParams<T> = _CollateralWithdrawal<T> &
|
110
|
+
_NoCollateralDeposit &
|
111
|
+
Partial<_ZUSDRepayment<T>> &
|
112
|
+
_NoZUSDBorrowing;
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Parameters of an {@link TransactableLiquity.adjustTrove | adjustTrove()} transaction.
|
116
|
+
*
|
117
|
+
* @remarks
|
118
|
+
* The type parameter `T` specifies the allowed value type(s) of the particular
|
119
|
+
* `TroveAdjustmentParams` object's properties.
|
120
|
+
*
|
121
|
+
* Even though all properties are optional, a valid `TroveAdjustmentParams` object must define at
|
122
|
+
* least one.
|
123
|
+
*
|
124
|
+
* Defining both `depositCollateral` and `withdrawCollateral`, or both `borrowZUSD` and `repayZUSD`
|
125
|
+
* at the same time is disallowed, and will result in a type-checking error.
|
126
|
+
*
|
127
|
+
* <h2>Properties</h2>
|
128
|
+
*
|
129
|
+
* <table>
|
130
|
+
*
|
131
|
+
* <tr>
|
132
|
+
* <th> Property </th>
|
133
|
+
* <th> Type </th>
|
134
|
+
* <th> Description </th>
|
135
|
+
* </tr>
|
136
|
+
*
|
137
|
+
* <tr>
|
138
|
+
* <td> depositCollateral? </td>
|
139
|
+
* <td> T </td>
|
140
|
+
* <td> <i>(Optional)</i> The amount of collateral that's deposited. </td>
|
141
|
+
* </tr>
|
142
|
+
*
|
143
|
+
* <tr>
|
144
|
+
* <td> withdrawCollateral? </td>
|
145
|
+
* <td> T </td>
|
146
|
+
* <td> <i>(Optional)</i> The amount of collateral that's withdrawn. </td>
|
147
|
+
* </tr>
|
148
|
+
*
|
149
|
+
* <tr>
|
150
|
+
* <td> borrowZUSD? </td>
|
151
|
+
* <td> T </td>
|
152
|
+
* <td> <i>(Optional)</i> The amount of ZUSD that's borrowed. </td>
|
153
|
+
* </tr>
|
154
|
+
*
|
155
|
+
* <tr>
|
156
|
+
* <td> repayZUSD? </td>
|
157
|
+
* <td> T </td>
|
158
|
+
* <td> <i>(Optional)</i> The amount of ZUSD that's repaid. </td>
|
159
|
+
* </tr>
|
160
|
+
*
|
161
|
+
* </table>
|
162
|
+
*
|
163
|
+
* @public
|
164
|
+
*/
|
165
|
+
export type TroveAdjustmentParams<T = unknown> =
|
166
|
+
| (_CollateralChange<T> & _NoDebtChange)
|
167
|
+
| (_DebtChange<T> & _NoCollateralChange)
|
168
|
+
| (_CollateralChange<T> & _DebtChange<T>);
|
169
|
+
|
170
|
+
/**
|
171
|
+
* Describes why a Trove could not be created.
|
172
|
+
*
|
173
|
+
* @remarks
|
174
|
+
* See {@link TroveChange}.
|
175
|
+
*
|
176
|
+
* <h2>Possible values</h2>
|
177
|
+
*
|
178
|
+
* <table>
|
179
|
+
*
|
180
|
+
* <tr>
|
181
|
+
* <th> Value </th>
|
182
|
+
* <th> Reason </th>
|
183
|
+
* </tr>
|
184
|
+
*
|
185
|
+
* <tr>
|
186
|
+
* <td> "missingLiquidationReserve" </td>
|
187
|
+
* <td> A Trove's debt cannot be less than the liquidation reserve. </td>
|
188
|
+
* </tr>
|
189
|
+
*
|
190
|
+
* </table>
|
191
|
+
*
|
192
|
+
* More errors may be added in the future.
|
193
|
+
*
|
194
|
+
* @public
|
195
|
+
*/
|
196
|
+
export type TroveCreationError = "missingLiquidationReserve";
|
197
|
+
|
198
|
+
/**
|
199
|
+
* Represents the change between two Trove states.
|
200
|
+
*
|
201
|
+
* @remarks
|
202
|
+
* Returned by {@link Trove.whatChanged}.
|
203
|
+
*
|
204
|
+
* Passed as a parameter to {@link Trove.apply}.
|
205
|
+
*
|
206
|
+
* @public
|
207
|
+
*/
|
208
|
+
export type TroveChange<T> =
|
209
|
+
| { type: "invalidCreation"; invalidTrove: Trove; error: TroveCreationError }
|
210
|
+
| { type: "creation"; params: TroveCreationParams<T> }
|
211
|
+
| { type: "closure"; params: TroveClosureParams<T> }
|
212
|
+
| { type: "adjustment"; params: TroveAdjustmentParams<T>; setToZero?: "collateral" | "debt" };
|
213
|
+
|
214
|
+
// This might seem backwards, but this way we avoid spamming the .d.ts and generated docs
|
215
|
+
type InvalidTroveCreation = Extract<TroveChange<never>, { type: "invalidCreation" }>;
|
216
|
+
type TroveCreation<T> = Extract<TroveChange<T>, { type: "creation" }>;
|
217
|
+
type TroveClosure<T> = Extract<TroveChange<T>, { type: "closure" }>;
|
218
|
+
type TroveAdjustment<T> = Extract<TroveChange<T>, { type: "adjustment" }>;
|
219
|
+
|
220
|
+
const invalidTroveCreation = (
|
221
|
+
invalidTrove: Trove,
|
222
|
+
error: TroveCreationError
|
223
|
+
): InvalidTroveCreation => ({
|
224
|
+
type: "invalidCreation",
|
225
|
+
invalidTrove,
|
226
|
+
error
|
227
|
+
});
|
228
|
+
|
229
|
+
const troveCreation = <T>(params: TroveCreationParams<T>): TroveCreation<T> => ({
|
230
|
+
type: "creation",
|
231
|
+
params
|
232
|
+
});
|
233
|
+
|
234
|
+
const troveClosure = <T>(params: TroveClosureParams<T>): TroveClosure<T> => ({
|
235
|
+
type: "closure",
|
236
|
+
params
|
237
|
+
});
|
238
|
+
|
239
|
+
const troveAdjustment = <T>(
|
240
|
+
params: TroveAdjustmentParams<T>,
|
241
|
+
setToZero?: "collateral" | "debt"
|
242
|
+
): TroveAdjustment<T> => ({
|
243
|
+
type: "adjustment",
|
244
|
+
params,
|
245
|
+
setToZero
|
246
|
+
});
|
247
|
+
|
248
|
+
const valueIsDefined = <T>(entry: [string, T | undefined]): entry is [string, T] =>
|
249
|
+
entry[1] !== undefined;
|
250
|
+
|
251
|
+
type AllowedKey<T> = Exclude<
|
252
|
+
{
|
253
|
+
[P in keyof T]: T[P] extends undefined ? never : P;
|
254
|
+
}[keyof T],
|
255
|
+
undefined
|
256
|
+
>;
|
257
|
+
|
258
|
+
const allowedTroveCreationKeys: AllowedKey<TroveCreationParams>[] = [
|
259
|
+
"depositCollateral",
|
260
|
+
"borrowZUSD"
|
261
|
+
];
|
262
|
+
|
263
|
+
function checkAllowedTroveCreationKeys<T>(
|
264
|
+
entries: [string, T][]
|
265
|
+
): asserts entries is [AllowedKey<TroveCreationParams>, T][] {
|
266
|
+
const badKeys = entries
|
267
|
+
.filter(([k]) => !(allowedTroveCreationKeys as string[]).includes(k))
|
268
|
+
.map(([k]) => `'${k}'`);
|
269
|
+
|
270
|
+
if (badKeys.length > 0) {
|
271
|
+
throw new Error(`TroveCreationParams: property ${badKeys.join(", ")} not allowed`);
|
272
|
+
}
|
273
|
+
}
|
274
|
+
|
275
|
+
const troveCreationParamsFromEntries = <T>(
|
276
|
+
entries: [AllowedKey<TroveCreationParams>, T][]
|
277
|
+
): TroveCreationParams<T> => {
|
278
|
+
const params = Object.fromEntries(entries) as Record<AllowedKey<TroveCreationParams>, T>;
|
279
|
+
const missingKeys = allowedTroveCreationKeys.filter(k => !(k in params)).map(k => `'${k}'`);
|
280
|
+
|
281
|
+
if (missingKeys.length > 0) {
|
282
|
+
throw new Error(`TroveCreationParams: property ${missingKeys.join(", ")} missing`);
|
283
|
+
}
|
284
|
+
|
285
|
+
return params;
|
286
|
+
};
|
287
|
+
|
288
|
+
const decimalize = <T>([k, v]: [T, Decimalish]): [T, Decimal] => [k, Decimal.from(v)];
|
289
|
+
const nonZero = <T>([, v]: [T, Decimal]): boolean => !v.isZero;
|
290
|
+
|
291
|
+
/** @internal */
|
292
|
+
export const _normalizeTroveCreation = (
|
293
|
+
params: Record<string, Decimalish | undefined>
|
294
|
+
): TroveCreationParams<Decimal> => {
|
295
|
+
const definedEntries = Object.entries(params).filter(valueIsDefined);
|
296
|
+
checkAllowedTroveCreationKeys(definedEntries);
|
297
|
+
const nonZeroEntries = definedEntries.map(decimalize);
|
298
|
+
|
299
|
+
return troveCreationParamsFromEntries(nonZeroEntries);
|
300
|
+
};
|
301
|
+
|
302
|
+
const allowedTroveAdjustmentKeys: AllowedKey<TroveAdjustmentParams>[] = [
|
303
|
+
"depositCollateral",
|
304
|
+
"withdrawCollateral",
|
305
|
+
"borrowZUSD",
|
306
|
+
"repayZUSD"
|
307
|
+
];
|
308
|
+
|
309
|
+
function checkAllowedTroveAdjustmentKeys<T>(
|
310
|
+
entries: [string, T][]
|
311
|
+
): asserts entries is [AllowedKey<TroveAdjustmentParams>, T][] {
|
312
|
+
const badKeys = entries
|
313
|
+
.filter(([k]) => !(allowedTroveAdjustmentKeys as string[]).includes(k))
|
314
|
+
.map(([k]) => `'${k}'`);
|
315
|
+
|
316
|
+
if (badKeys.length > 0) {
|
317
|
+
throw new Error(`TroveAdjustmentParams: property ${badKeys.join(", ")} not allowed`);
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|
321
|
+
const collateralChangeFrom = <T>({
|
322
|
+
depositCollateral,
|
323
|
+
withdrawCollateral
|
324
|
+
}: Partial<Record<AllowedKey<TroveAdjustmentParams>, T>>): _CollateralChange<T> | undefined => {
|
325
|
+
if (depositCollateral !== undefined && withdrawCollateral !== undefined) {
|
326
|
+
throw new Error(
|
327
|
+
"TroveAdjustmentParams: 'depositCollateral' and 'withdrawCollateral' " +
|
328
|
+
"can't be present at the same time"
|
329
|
+
);
|
330
|
+
}
|
331
|
+
|
332
|
+
if (depositCollateral !== undefined) {
|
333
|
+
return { depositCollateral };
|
334
|
+
}
|
335
|
+
|
336
|
+
if (withdrawCollateral !== undefined) {
|
337
|
+
return { withdrawCollateral };
|
338
|
+
}
|
339
|
+
};
|
340
|
+
|
341
|
+
const debtChangeFrom = <T>({
|
342
|
+
borrowZUSD,
|
343
|
+
repayZUSD
|
344
|
+
}: Partial<Record<AllowedKey<TroveAdjustmentParams>, T>>): _DebtChange<T> | undefined => {
|
345
|
+
if (borrowZUSD !== undefined && repayZUSD !== undefined) {
|
346
|
+
throw new Error(
|
347
|
+
"TroveAdjustmentParams: 'borrowZUSD' and 'repayZUSD' can't be present at the same time"
|
348
|
+
);
|
349
|
+
}
|
350
|
+
|
351
|
+
if (borrowZUSD !== undefined) {
|
352
|
+
return { borrowZUSD };
|
353
|
+
}
|
354
|
+
|
355
|
+
if (repayZUSD !== undefined) {
|
356
|
+
return { repayZUSD };
|
357
|
+
}
|
358
|
+
};
|
359
|
+
|
360
|
+
const troveAdjustmentParamsFromEntries = <T>(
|
361
|
+
entries: [AllowedKey<TroveAdjustmentParams>, T][]
|
362
|
+
): TroveAdjustmentParams<T> => {
|
363
|
+
const params = Object.fromEntries(entries) as Partial<
|
364
|
+
Record<AllowedKey<TroveAdjustmentParams>, T>
|
365
|
+
>;
|
366
|
+
|
367
|
+
const collateralChange = collateralChangeFrom(params);
|
368
|
+
const debtChange = debtChangeFrom(params);
|
369
|
+
|
370
|
+
if (collateralChange !== undefined && debtChange !== undefined) {
|
371
|
+
return { ...collateralChange, ...debtChange };
|
372
|
+
}
|
373
|
+
|
374
|
+
if (collateralChange !== undefined) {
|
375
|
+
return collateralChange;
|
376
|
+
}
|
377
|
+
|
378
|
+
if (debtChange !== undefined) {
|
379
|
+
return debtChange;
|
380
|
+
}
|
381
|
+
|
382
|
+
throw new Error("TroveAdjustmentParams: must include at least one non-zero parameter");
|
383
|
+
};
|
384
|
+
|
385
|
+
/** @internal */
|
386
|
+
export const _normalizeTroveAdjustment = (
|
387
|
+
params: Record<string, Decimalish | undefined>
|
388
|
+
): TroveAdjustmentParams<Decimal> => {
|
389
|
+
const definedEntries = Object.entries(params).filter(valueIsDefined);
|
390
|
+
checkAllowedTroveAdjustmentKeys(definedEntries);
|
391
|
+
const nonZeroEntries = definedEntries.map(decimalize).filter(nonZero);
|
392
|
+
|
393
|
+
return troveAdjustmentParamsFromEntries(nonZeroEntries);
|
394
|
+
};
|
395
|
+
|
396
|
+
const applyFee = (borrowingRate: Decimalish, debtIncrease: Decimal) =>
|
397
|
+
debtIncrease.mul(Decimal.ONE.add(borrowingRate));
|
398
|
+
|
399
|
+
const unapplyFee = (borrowingRate: Decimalish, debtIncrease: Decimal) =>
|
400
|
+
debtIncrease._divCeil(Decimal.ONE.add(borrowingRate));
|
401
|
+
|
402
|
+
const NOMINAL_COLLATERAL_RATIO_PRECISION = Decimal.from(100);
|
403
|
+
|
404
|
+
/**
|
405
|
+
* A combination of collateral and debt.
|
406
|
+
*
|
407
|
+
* @public
|
408
|
+
*/
|
409
|
+
export class Trove {
|
410
|
+
/** Amount of native currency (e.g. Ether) collateralized. */
|
411
|
+
readonly collateral: Decimal;
|
412
|
+
|
413
|
+
/** Amount of ZUSD owed. */
|
414
|
+
readonly debt: Decimal;
|
415
|
+
|
416
|
+
/** @internal */
|
417
|
+
constructor(collateral = Decimal.ZERO, debt = Decimal.ZERO) {
|
418
|
+
this.collateral = collateral;
|
419
|
+
this.debt = debt;
|
420
|
+
}
|
421
|
+
|
422
|
+
get isEmpty(): boolean {
|
423
|
+
return this.collateral.isZero && this.debt.isZero;
|
424
|
+
}
|
425
|
+
|
426
|
+
/**
|
427
|
+
* Amount of ZUSD that must be repaid to close this Trove.
|
428
|
+
*
|
429
|
+
* @remarks
|
430
|
+
* This doesn't include the liquidation reserve, which is refunded in case of normal closure.
|
431
|
+
*/
|
432
|
+
get netDebt(): Decimal {
|
433
|
+
if (this.debt.lt(ZUSD_LIQUIDATION_RESERVE)) {
|
434
|
+
throw new Error(`netDebt should not be used when debt < ${ZUSD_LIQUIDATION_RESERVE}`);
|
435
|
+
}
|
436
|
+
|
437
|
+
return this.debt.sub(ZUSD_LIQUIDATION_RESERVE);
|
438
|
+
}
|
439
|
+
|
440
|
+
/** @internal */
|
441
|
+
get _nominalCollateralRatio(): Decimal {
|
442
|
+
return this.collateral.mulDiv(NOMINAL_COLLATERAL_RATIO_PRECISION, this.debt);
|
443
|
+
}
|
444
|
+
|
445
|
+
/** Calculate the Trove's collateralization ratio at a given price. */
|
446
|
+
collateralRatio(price: Decimalish): Decimal {
|
447
|
+
return this.collateral.mulDiv(price, this.debt);
|
448
|
+
}
|
449
|
+
|
450
|
+
/**
|
451
|
+
* Whether the Trove is undercollateralized at a given price.
|
452
|
+
*
|
453
|
+
* @returns
|
454
|
+
* `true` if the Trove's collateralization ratio is less than the
|
455
|
+
* {@link MINIMUM_COLLATERAL_RATIO}.
|
456
|
+
*/
|
457
|
+
collateralRatioIsBelowMinimum(price: Decimalish): boolean {
|
458
|
+
return this.collateralRatio(price).lt(MINIMUM_COLLATERAL_RATIO);
|
459
|
+
}
|
460
|
+
|
461
|
+
/**
|
462
|
+
* Whether the collateralization ratio is less than the {@link CRITICAL_COLLATERAL_RATIO} at a
|
463
|
+
* given price.
|
464
|
+
*
|
465
|
+
* @example
|
466
|
+
* Can be used to check whether the Zero protocol is in recovery mode by using it on the return
|
467
|
+
* value of {@link ReadableLiquity.getTotal | getTotal()}. For example:
|
468
|
+
*
|
469
|
+
* ```typescript
|
470
|
+
* const total = await zero.getTotal();
|
471
|
+
* const price = await zero.getPrice();
|
472
|
+
*
|
473
|
+
* if (total.collateralRatioIsBelowCritical(price)) {
|
474
|
+
* // Recovery mode is active
|
475
|
+
* }
|
476
|
+
* ```
|
477
|
+
*/
|
478
|
+
collateralRatioIsBelowCritical(price: Decimalish): boolean {
|
479
|
+
return this.collateralRatio(price).lt(CRITICAL_COLLATERAL_RATIO);
|
480
|
+
}
|
481
|
+
|
482
|
+
/** Whether the Trove is sufficiently collateralized to be opened during recovery mode. */
|
483
|
+
isOpenableInRecoveryMode(price: Decimalish): boolean {
|
484
|
+
return this.collateralRatio(price).gte(CRITICAL_COLLATERAL_RATIO);
|
485
|
+
}
|
486
|
+
|
487
|
+
/** @internal */
|
488
|
+
toString(): string {
|
489
|
+
return `{ collateral: ${this.collateral}, debt: ${this.debt} }`;
|
490
|
+
}
|
491
|
+
|
492
|
+
equals(that: Trove): boolean {
|
493
|
+
return this.collateral.eq(that.collateral) && this.debt.eq(that.debt);
|
494
|
+
}
|
495
|
+
|
496
|
+
add(that: Trove): Trove {
|
497
|
+
return new Trove(this.collateral.add(that.collateral), this.debt.add(that.debt));
|
498
|
+
}
|
499
|
+
|
500
|
+
addCollateral(collateral: Decimalish): Trove {
|
501
|
+
return new Trove(this.collateral.add(collateral), this.debt);
|
502
|
+
}
|
503
|
+
|
504
|
+
addDebt(debt: Decimalish): Trove {
|
505
|
+
return new Trove(this.collateral, this.debt.add(debt));
|
506
|
+
}
|
507
|
+
|
508
|
+
subtract(that: Trove): Trove {
|
509
|
+
const { collateral, debt } = that;
|
510
|
+
|
511
|
+
return new Trove(
|
512
|
+
this.collateral.gt(collateral) ? this.collateral.sub(collateral) : Decimal.ZERO,
|
513
|
+
this.debt.gt(debt) ? this.debt.sub(debt) : Decimal.ZERO
|
514
|
+
);
|
515
|
+
}
|
516
|
+
|
517
|
+
subtractCollateral(collateral: Decimalish): Trove {
|
518
|
+
return new Trove(
|
519
|
+
this.collateral.gt(collateral) ? this.collateral.sub(collateral) : Decimal.ZERO,
|
520
|
+
this.debt
|
521
|
+
);
|
522
|
+
}
|
523
|
+
|
524
|
+
subtractDebt(debt: Decimalish): Trove {
|
525
|
+
return new Trove(this.collateral, this.debt.gt(debt) ? this.debt.sub(debt) : Decimal.ZERO);
|
526
|
+
}
|
527
|
+
|
528
|
+
multiply(multiplier: Decimalish): Trove {
|
529
|
+
return new Trove(this.collateral.mul(multiplier), this.debt.mul(multiplier));
|
530
|
+
}
|
531
|
+
|
532
|
+
setCollateral(collateral: Decimalish): Trove {
|
533
|
+
return new Trove(Decimal.from(collateral), this.debt);
|
534
|
+
}
|
535
|
+
|
536
|
+
setDebt(debt: Decimalish): Trove {
|
537
|
+
return new Trove(this.collateral, Decimal.from(debt));
|
538
|
+
}
|
539
|
+
|
540
|
+
private _debtChange({ debt }: Trove, borrowingRate: Decimalish): _DebtChange<Decimal> {
|
541
|
+
return debt.gt(this.debt)
|
542
|
+
? { borrowZUSD: unapplyFee(borrowingRate, debt.sub(this.debt)) }
|
543
|
+
: { repayZUSD: this.debt.sub(debt) };
|
544
|
+
}
|
545
|
+
|
546
|
+
private _collateralChange({ collateral }: Trove): _CollateralChange<Decimal> {
|
547
|
+
return collateral.gt(this.collateral)
|
548
|
+
? { depositCollateral: collateral.sub(this.collateral) }
|
549
|
+
: { withdrawCollateral: this.collateral.sub(collateral) };
|
550
|
+
}
|
551
|
+
|
552
|
+
/**
|
553
|
+
* Calculate the difference between this Trove and another.
|
554
|
+
*
|
555
|
+
* @param that - The other Trove.
|
556
|
+
* @param borrowingRate - Borrowing rate to use when calculating a borrowed amount.
|
557
|
+
*
|
558
|
+
* @returns
|
559
|
+
* An object representing the change, or `undefined` if the Troves are equal.
|
560
|
+
*/
|
561
|
+
whatChanged(
|
562
|
+
that: Trove,
|
563
|
+
borrowingRate: Decimalish = MINIMUM_BORROWING_RATE
|
564
|
+
): TroveChange<Decimal> | undefined {
|
565
|
+
if (this.collateral.eq(that.collateral) && this.debt.eq(that.debt)) {
|
566
|
+
return undefined;
|
567
|
+
}
|
568
|
+
|
569
|
+
if (this.isEmpty) {
|
570
|
+
if (that.debt.lt(ZUSD_LIQUIDATION_RESERVE)) {
|
571
|
+
return invalidTroveCreation(that, "missingLiquidationReserve");
|
572
|
+
}
|
573
|
+
|
574
|
+
return troveCreation({
|
575
|
+
depositCollateral: that.collateral,
|
576
|
+
borrowZUSD: unapplyFee(borrowingRate, that.netDebt)
|
577
|
+
});
|
578
|
+
}
|
579
|
+
|
580
|
+
if (that.isEmpty) {
|
581
|
+
return troveClosure(
|
582
|
+
this.netDebt.nonZero
|
583
|
+
? { withdrawCollateral: this.collateral, repayZUSD: this.netDebt }
|
584
|
+
: { withdrawCollateral: this.collateral }
|
585
|
+
);
|
586
|
+
}
|
587
|
+
|
588
|
+
return this.collateral.eq(that.collateral)
|
589
|
+
? troveAdjustment<Decimal>(this._debtChange(that, borrowingRate), that.debt.zero && "debt")
|
590
|
+
: this.debt.eq(that.debt)
|
591
|
+
? troveAdjustment<Decimal>(this._collateralChange(that), that.collateral.zero && "collateral")
|
592
|
+
: troveAdjustment<Decimal>(
|
593
|
+
{
|
594
|
+
...this._debtChange(that, borrowingRate),
|
595
|
+
...this._collateralChange(that)
|
596
|
+
},
|
597
|
+
(that.debt.zero && "debt") ?? (that.collateral.zero && "collateral")
|
598
|
+
);
|
599
|
+
}
|
600
|
+
|
601
|
+
/**
|
602
|
+
* Make a new Trove by applying a {@link TroveChange} to this Trove.
|
603
|
+
*
|
604
|
+
* @param change - The change to apply.
|
605
|
+
* @param borrowingRate - Borrowing rate to use when adding a borrowed amount to the Trove's debt.
|
606
|
+
*/
|
607
|
+
apply(
|
608
|
+
change: TroveChange<Decimal> | undefined,
|
609
|
+
borrowingRate: Decimalish = MINIMUM_BORROWING_RATE
|
610
|
+
): Trove {
|
611
|
+
if (!change) {
|
612
|
+
return this;
|
613
|
+
}
|
614
|
+
|
615
|
+
switch (change.type) {
|
616
|
+
case "invalidCreation":
|
617
|
+
if (!this.isEmpty) {
|
618
|
+
throw new Error("Can't create onto existing Trove");
|
619
|
+
}
|
620
|
+
|
621
|
+
return change.invalidTrove;
|
622
|
+
|
623
|
+
case "creation": {
|
624
|
+
if (!this.isEmpty) {
|
625
|
+
throw new Error("Can't create onto existing Trove");
|
626
|
+
}
|
627
|
+
|
628
|
+
const { depositCollateral, borrowZUSD } = change.params;
|
629
|
+
|
630
|
+
return new Trove(
|
631
|
+
depositCollateral,
|
632
|
+
ZUSD_LIQUIDATION_RESERVE.add(applyFee(borrowingRate, borrowZUSD))
|
633
|
+
);
|
634
|
+
}
|
635
|
+
|
636
|
+
case "closure":
|
637
|
+
if (this.isEmpty) {
|
638
|
+
throw new Error("Can't close empty Trove");
|
639
|
+
}
|
640
|
+
|
641
|
+
return _emptyTrove;
|
642
|
+
|
643
|
+
case "adjustment": {
|
644
|
+
const {
|
645
|
+
setToZero,
|
646
|
+
params: { depositCollateral, withdrawCollateral, borrowZUSD, repayZUSD }
|
647
|
+
} = change;
|
648
|
+
|
649
|
+
const collateralDecrease = withdrawCollateral ?? Decimal.ZERO;
|
650
|
+
const collateralIncrease = depositCollateral ?? Decimal.ZERO;
|
651
|
+
const debtDecrease = repayZUSD ?? Decimal.ZERO;
|
652
|
+
const debtIncrease = borrowZUSD ? applyFee(borrowingRate, borrowZUSD) : Decimal.ZERO;
|
653
|
+
|
654
|
+
return setToZero === "collateral"
|
655
|
+
? this.setCollateral(Decimal.ZERO).addDebt(debtIncrease).subtractDebt(debtDecrease)
|
656
|
+
: setToZero === "debt"
|
657
|
+
? this.setDebt(Decimal.ZERO)
|
658
|
+
.addCollateral(collateralIncrease)
|
659
|
+
.subtractCollateral(collateralDecrease)
|
660
|
+
: this.add(new Trove(collateralIncrease, debtIncrease)).subtract(
|
661
|
+
new Trove(collateralDecrease, debtDecrease)
|
662
|
+
);
|
663
|
+
}
|
664
|
+
}
|
665
|
+
}
|
666
|
+
|
667
|
+
/**
|
668
|
+
* Calculate the result of an {@link TransactableLiquity.openTrove | openTrove()} transaction.
|
669
|
+
*
|
670
|
+
* @param params - Parameters of the transaction.
|
671
|
+
* @param borrowingRate - Borrowing rate to use when calculating the Trove's debt.
|
672
|
+
*/
|
673
|
+
static create(params: TroveCreationParams<Decimalish>, borrowingRate?: Decimalish): Trove {
|
674
|
+
return _emptyTrove.apply(troveCreation(_normalizeTroveCreation(params)), borrowingRate);
|
675
|
+
}
|
676
|
+
|
677
|
+
/**
|
678
|
+
* Calculate the parameters of an {@link TransactableLiquity.openTrove | openTrove()} transaction
|
679
|
+
* that will result in the given Trove.
|
680
|
+
*
|
681
|
+
* @param that - The Trove to recreate.
|
682
|
+
* @param borrowingRate - Current borrowing rate.
|
683
|
+
*/
|
684
|
+
static recreate(that: Trove, borrowingRate?: Decimalish): TroveCreationParams<Decimal> {
|
685
|
+
const change = _emptyTrove.whatChanged(that, borrowingRate);
|
686
|
+
assert(change?.type === "creation");
|
687
|
+
return change.params;
|
688
|
+
}
|
689
|
+
|
690
|
+
/**
|
691
|
+
* Calculate the result of an {@link TransactableLiquity.adjustTrove | adjustTrove()} transaction
|
692
|
+
* on this Trove.
|
693
|
+
*
|
694
|
+
* @param params - Parameters of the transaction.
|
695
|
+
* @param borrowingRate - Borrowing rate to use when adding to the Trove's debt.
|
696
|
+
*/
|
697
|
+
adjust(params: TroveAdjustmentParams<Decimalish>, borrowingRate?: Decimalish): Trove {
|
698
|
+
return this.apply(troveAdjustment(_normalizeTroveAdjustment(params)), borrowingRate);
|
699
|
+
}
|
700
|
+
|
701
|
+
/**
|
702
|
+
* Calculate the parameters of an {@link TransactableLiquity.adjustTrove | adjustTrove()}
|
703
|
+
* transaction that will change this Trove into the given Trove.
|
704
|
+
*
|
705
|
+
* @param that - The desired result of the transaction.
|
706
|
+
* @param borrowingRate - Current borrowing rate.
|
707
|
+
*/
|
708
|
+
adjustTo(that: Trove, borrowingRate?: Decimalish): TroveAdjustmentParams<Decimal> {
|
709
|
+
const change = this.whatChanged(that, borrowingRate);
|
710
|
+
assert(change?.type === "adjustment");
|
711
|
+
return change.params;
|
712
|
+
}
|
713
|
+
}
|
714
|
+
|
715
|
+
/** @internal */
|
716
|
+
export const _emptyTrove = new Trove();
|
717
|
+
|
718
|
+
/**
|
719
|
+
* Represents whether a UserTrove is open or not, or why it was closed.
|
720
|
+
*
|
721
|
+
* @public
|
722
|
+
*/
|
723
|
+
export type UserTroveStatus =
|
724
|
+
| "nonExistent"
|
725
|
+
| "open"
|
726
|
+
| "closedByOwner"
|
727
|
+
| "closedByLiquidation"
|
728
|
+
| "closedByRedemption";
|
729
|
+
|
730
|
+
/**
|
731
|
+
* A Trove that is associated with a single owner.
|
732
|
+
*
|
733
|
+
* @remarks
|
734
|
+
* The SDK uses the base {@link Trove} class as a generic container of collateral and debt, for
|
735
|
+
* example to represent the {@link ReadableLiquity.getTotal | total collateral and debt} locked up
|
736
|
+
* in the protocol.
|
737
|
+
*
|
738
|
+
* The `UserTrove` class extends `Trove` with extra information that's only available for Troves
|
739
|
+
* that are associated with a single owner (such as the owner's address, or the Trove's status).
|
740
|
+
*
|
741
|
+
* @public
|
742
|
+
*/
|
743
|
+
export class UserTrove extends Trove {
|
744
|
+
/** Address that owns this Trove. */
|
745
|
+
readonly ownerAddress: string;
|
746
|
+
|
747
|
+
/** Provides more information when the UserTrove is empty. */
|
748
|
+
readonly status: UserTroveStatus;
|
749
|
+
|
750
|
+
/** @internal */
|
751
|
+
constructor(ownerAddress: string, status: UserTroveStatus, collateral?: Decimal, debt?: Decimal) {
|
752
|
+
super(collateral, debt);
|
753
|
+
|
754
|
+
this.ownerAddress = ownerAddress;
|
755
|
+
this.status = status;
|
756
|
+
}
|
757
|
+
|
758
|
+
equals(that: UserTrove): boolean {
|
759
|
+
return (
|
760
|
+
super.equals(that) && this.ownerAddress === that.ownerAddress && this.status === that.status
|
761
|
+
);
|
762
|
+
}
|
763
|
+
|
764
|
+
/** @internal */
|
765
|
+
toString(): string {
|
766
|
+
return (
|
767
|
+
`{ ownerAddress: "${this.ownerAddress}"` +
|
768
|
+
`, collateral: ${this.collateral}` +
|
769
|
+
`, debt: ${this.debt}` +
|
770
|
+
`, status: "${this.status}" }`
|
771
|
+
);
|
772
|
+
}
|
773
|
+
}
|
774
|
+
|
775
|
+
/**
|
776
|
+
* A Trove in its state after the last direct modification.
|
777
|
+
*
|
778
|
+
* @remarks
|
779
|
+
* The Trove may have received collateral and debt shares from liquidations since then.
|
780
|
+
* Use {@link TroveWithPendingRedistribution.applyRedistribution | applyRedistribution()} to
|
781
|
+
* calculate the Trove's most up-to-date state.
|
782
|
+
*
|
783
|
+
* @public
|
784
|
+
*/
|
785
|
+
export class TroveWithPendingRedistribution extends UserTrove {
|
786
|
+
private readonly stake: Decimal;
|
787
|
+
private readonly snapshotOfTotalRedistributed: Trove;
|
788
|
+
|
789
|
+
/** @internal */
|
790
|
+
constructor(
|
791
|
+
ownerAddress: string,
|
792
|
+
status: UserTroveStatus,
|
793
|
+
collateral?: Decimal,
|
794
|
+
debt?: Decimal,
|
795
|
+
stake = Decimal.ZERO,
|
796
|
+
snapshotOfTotalRedistributed = _emptyTrove
|
797
|
+
) {
|
798
|
+
super(ownerAddress, status, collateral, debt);
|
799
|
+
|
800
|
+
this.stake = stake;
|
801
|
+
this.snapshotOfTotalRedistributed = snapshotOfTotalRedistributed;
|
802
|
+
}
|
803
|
+
|
804
|
+
applyRedistribution(totalRedistributed: Trove): UserTrove {
|
805
|
+
const afterRedistribution = this.add(
|
806
|
+
totalRedistributed.subtract(this.snapshotOfTotalRedistributed).multiply(this.stake)
|
807
|
+
);
|
808
|
+
|
809
|
+
return new UserTrove(
|
810
|
+
this.ownerAddress,
|
811
|
+
this.status,
|
812
|
+
afterRedistribution.collateral,
|
813
|
+
afterRedistribution.debt
|
814
|
+
);
|
815
|
+
}
|
816
|
+
|
817
|
+
equals(that: TroveWithPendingRedistribution): boolean {
|
818
|
+
return (
|
819
|
+
super.equals(that) &&
|
820
|
+
this.stake.eq(that.stake) &&
|
821
|
+
this.snapshotOfTotalRedistributed.equals(that.snapshotOfTotalRedistributed)
|
822
|
+
);
|
823
|
+
}
|
824
|
+
}
|