@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.
Files changed (84) hide show
  1. package/.eslintrc.json +17 -0
  2. package/.mocharc.yml +1 -0
  3. package/LICENSE +905 -0
  4. package/README.md +23 -0
  5. package/api-extractor.json +4 -0
  6. package/dist/index.d.ts +14 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +26 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/src/Decimal.d.ts +89 -0
  11. package/dist/src/Decimal.d.ts.map +1 -0
  12. package/dist/src/Decimal.js +361 -0
  13. package/dist/src/Decimal.js.map +1 -0
  14. package/dist/src/Fees.d.ts +82 -0
  15. package/dist/src/Fees.d.ts.map +1 -0
  16. package/dist/src/Fees.js +123 -0
  17. package/dist/src/Fees.js.map +1 -0
  18. package/dist/src/LiquityStore.d.ts +209 -0
  19. package/dist/src/LiquityStore.d.ts.map +1 -0
  20. package/dist/src/LiquityStore.js +209 -0
  21. package/dist/src/LiquityStore.js.map +1 -0
  22. package/dist/src/ObservableLiquity.d.ts +15 -0
  23. package/dist/src/ObservableLiquity.d.ts.map +1 -0
  24. package/dist/src/ObservableLiquity.js +3 -0
  25. package/dist/src/ObservableLiquity.js.map +1 -0
  26. package/dist/src/PopulatableLiquity.d.ts +125 -0
  27. package/dist/src/PopulatableLiquity.d.ts.map +1 -0
  28. package/dist/src/PopulatableLiquity.js +3 -0
  29. package/dist/src/PopulatableLiquity.js.map +1 -0
  30. package/dist/src/ReadableLiquity.d.ts +156 -0
  31. package/dist/src/ReadableLiquity.d.ts.map +1 -0
  32. package/dist/src/ReadableLiquity.js +3 -0
  33. package/dist/src/ReadableLiquity.js.map +1 -0
  34. package/dist/src/SendableLiquity.d.ts +156 -0
  35. package/dist/src/SendableLiquity.d.ts.map +1 -0
  36. package/dist/src/SendableLiquity.js +20 -0
  37. package/dist/src/SendableLiquity.js.map +1 -0
  38. package/dist/src/StabilityDeposit.d.ts +59 -0
  39. package/dist/src/StabilityDeposit.d.ts.map +1 -0
  40. package/dist/src/StabilityDeposit.js +80 -0
  41. package/dist/src/StabilityDeposit.js.map +1 -0
  42. package/dist/src/TransactableLiquity.d.ts +414 -0
  43. package/dist/src/TransactableLiquity.d.ts.map +1 -0
  44. package/dist/src/TransactableLiquity.js +18 -0
  45. package/dist/src/TransactableLiquity.js.map +1 -0
  46. package/dist/src/Trove.d.ts +367 -0
  47. package/dist/src/Trove.d.ts.map +1 -0
  48. package/dist/src/Trove.js +423 -0
  49. package/dist/src/Trove.js.map +1 -0
  50. package/dist/src/ZEROStake.d.ts +52 -0
  51. package/dist/src/ZEROStake.d.ts.map +1 -0
  52. package/dist/src/ZEROStake.js +74 -0
  53. package/dist/src/ZEROStake.js.map +1 -0
  54. package/dist/src/_CachedReadableLiquity.d.ts +55 -0
  55. package/dist/src/_CachedReadableLiquity.d.ts.map +1 -0
  56. package/dist/src/_CachedReadableLiquity.js +93 -0
  57. package/dist/src/_CachedReadableLiquity.js.map +1 -0
  58. package/dist/src/constants.d.ts +61 -0
  59. package/dist/src/constants.d.ts.map +1 -0
  60. package/dist/src/constants.js +64 -0
  61. package/dist/src/constants.js.map +1 -0
  62. package/dist/tsdoc-metadata.json +11 -0
  63. package/etc/lib-base.api.md +788 -0
  64. package/index.ts +13 -0
  65. package/package.json +52 -0
  66. package/src/Decimal.ts +456 -0
  67. package/src/Fees.ts +160 -0
  68. package/src/LiquityStore.ts +563 -0
  69. package/src/ObservableLiquity.ts +32 -0
  70. package/src/PopulatableLiquity.ts +280 -0
  71. package/src/ReadableLiquity.ts +175 -0
  72. package/src/SendableLiquity.ts +251 -0
  73. package/src/StabilityDeposit.ts +126 -0
  74. package/src/TransactableLiquity.ts +471 -0
  75. package/src/Trove.ts +824 -0
  76. package/src/ZEROStake.ts +99 -0
  77. package/src/_CachedReadableLiquity.ts +186 -0
  78. package/src/constants.ts +68 -0
  79. package/test/Decimal.test.ts +212 -0
  80. package/test/StabilityDeposit.test.ts +30 -0
  81. package/test/Trove.test.ts +143 -0
  82. package/test/ZEROStake.test.ts +24 -0
  83. package/tsconfig.dist.json +8 -0
  84. 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
+ }