@carrot-protocol/clend-rpc 0.0.1-mrgn-fork1-dev-7be6ef2

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.
@@ -0,0 +1,867 @@
1
+ import { expect } from "chai";
2
+ import {
3
+ computeAdjustLeverageAmounts,
4
+ computeMaxLeverage,
5
+ computeLoopingAmounts,
6
+ calculateWeightedValue,
7
+ calculateWeightedLeverage,
8
+ calculateRequiredBorrowAmount,
9
+ computeWithdrawLeverageAmounts,
10
+ computeLiquidationThreshold,
11
+ } from "../src/math";
12
+
13
+ describe("Leverage Calculations", () => {
14
+ describe("calculateWeightedLeverage", () => {
15
+ it("should calculate correct leverage for 2x position", () => {
16
+ const leverage = calculateWeightedLeverage(1000, 1, 1, 500, 1, 1);
17
+ expect(leverage).to.be.closeTo(2, 0.01);
18
+ });
19
+
20
+ it("should calculate correct leverage for 3x position", () => {
21
+ const leverage = calculateWeightedLeverage(1000, 1, 1, 666.67, 1, 1);
22
+ expect(leverage).to.be.closeTo(3, 0.01);
23
+ });
24
+
25
+ it("should handle different prices for collateral and debt", () => {
26
+ const leverage = calculateWeightedLeverage(1000, 2, 1, 500, 1, 1);
27
+ expect(leverage).to.be.closeTo(1.33, 0.01);
28
+ });
29
+
30
+ it("should throw error for zero net value", () => {
31
+ expect(() => calculateWeightedLeverage(1000, 1, 1, 1000, 1, 1)).to.throw(
32
+ "Net value must be positive"
33
+ );
34
+ });
35
+
36
+ it("should calculate correct leverage with conservative weights (0.8, 1.2)", () => {
37
+ // With collateral weight = 0.8 and debt weight = 1.2
38
+ // weightedCollateral = 1000 * 0.8 = 800
39
+ // weightedDebt = 500 * 1.2 = 600
40
+ // leverage = 800 / (800 - 600) = 4
41
+ const leverage = calculateWeightedLeverage(1000, 1, 0.8, 500, 1, 1.2);
42
+ expect(leverage).to.be.closeTo(4, 0.01);
43
+ });
44
+
45
+ it("should calculate correct leverage with aggressive weights (0.9, 1.1)", () => {
46
+ // With collateral weight = 0.9 and debt weight = 1.1
47
+ // weightedCollateral = 1000 * 0.9 = 900
48
+ // weightedDebt = 500 * 1.1 = 550
49
+ // leverage = 900 / (900 - 550) = 2.5714
50
+ const leverage = calculateWeightedLeverage(1000, 1, 0.9, 500, 1, 1.1);
51
+ expect(leverage).to.be.closeTo(2.5714, 0.01);
52
+ });
53
+
54
+ it("should calculate correct leverage with very conservative weights (0.7, 1.3)", () => {
55
+ // With collateral weight = 0.7 and debt weight = 1.3
56
+ // weightedCollateral = 1000 * 0.7 = 700
57
+ // weightedDebt = 500 * 1.3 = 650
58
+ // leverage = 700 / (700 - 650) = 14
59
+ const leverage = calculateWeightedLeverage(1000, 1, 0.7, 500, 1, 1.3);
60
+ expect(leverage).to.be.closeTo(14, 0.01);
61
+ });
62
+
63
+ it("should calculate correct leverage with different weights and prices", () => {
64
+ // With collateral weight = 0.8, debt weight = 1.2
65
+ // collateral price = 2, debt price = 1
66
+ // weightedCollateral = 1000 * 2 * 0.8 = 1600
67
+ // weightedDebt = 500 * 1 * 1.2 = 600
68
+ // leverage = 1600 / (1600 - 600) = 1.6
69
+ const leverage = calculateWeightedLeverage(1000, 2, 0.8, 500, 1, 1.2);
70
+ expect(leverage).to.be.closeTo(1.6, 0.01);
71
+ });
72
+
73
+ it("should throw error when weighted debt exceeds weighted collateral", () => {
74
+ // With collateral weight = 0.5 and debt weight = 1.5
75
+ // weightedCollateral = 1000 * 0.5 = 500
76
+ // weightedDebt = 500 * 1.5 = 750
77
+ // This should throw because 500 - 750 = -250 (negative net value)
78
+ expect(() => calculateWeightedLeverage(1000, 1, 0.5, 500, 1, 1.5)).to.throw(
79
+ "Net value must be positive"
80
+ );
81
+ });
82
+
83
+ it("should calculate correct leverage ratio for simple position", () => {
84
+ const collateralAmount = 1000;
85
+ const collateralPrice = 1;
86
+ const collateralWeight = 1;
87
+ const debtAmount = 500;
88
+ const debtPrice = 1;
89
+ const debtWeight = 1;
90
+
91
+ const leverage = calculateWeightedLeverage(
92
+ collateralAmount,
93
+ collateralPrice,
94
+ collateralWeight,
95
+ debtAmount,
96
+ debtPrice,
97
+ debtWeight
98
+ );
99
+
100
+ // Leverage = 1000 / (1000 - 500) = 2x
101
+ expect(leverage).to.be.closeTo(2, 0.01);
102
+ });
103
+
104
+ it("should calculate correct leverage with weights", () => {
105
+ const collateralAmount = 1000;
106
+ const collateralPrice = 1;
107
+ const collateralWeight = 0.8; // 20% haircut
108
+ const debtAmount = 400;
109
+ const debtPrice = 1;
110
+ const debtWeight = 1.1; // 10% buffer
111
+
112
+ const leverage = calculateWeightedLeverage(
113
+ collateralAmount,
114
+ collateralPrice,
115
+ collateralWeight,
116
+ debtAmount,
117
+ debtPrice,
118
+ debtWeight
119
+ );
120
+
121
+ // Weighted collateral = 1000 * 1 * 0.8 = 800
122
+ // Weighted debt = 400 * 1 * 1.1 = 440
123
+ // Net value = 800 - 440 = 360
124
+ // Leverage = 800 / 360 = 2.222...
125
+ expect(leverage).to.be.closeTo(2.222, 0.001);
126
+ });
127
+
128
+ it("should throw error when net value is zero or negative", () => {
129
+ const collateralAmount = 1000;
130
+ const collateralPrice = 1;
131
+ const collateralWeight = 0.8;
132
+ const debtAmount = 800;
133
+ const debtPrice = 1;
134
+ const debtWeight = 1.1;
135
+
136
+ // Weighted collateral = 1000 * 1 * 0.8 = 800
137
+ // Weighted debt = 800 * 1 * 1.1 = 880
138
+ // Net value = 800 - 880 = -80 (negative)
139
+ expect(() => {
140
+ calculateWeightedLeverage(
141
+ collateralAmount,
142
+ collateralPrice,
143
+ collateralWeight,
144
+ debtAmount,
145
+ debtPrice,
146
+ debtWeight
147
+ );
148
+ }).to.throw("Net value must be positive");
149
+ });
150
+ });
151
+
152
+ describe("computeAdjustLeverageAmounts", () => {
153
+ it("should calculate correct adjustments for increasing leverage", () => {
154
+ const currentCollateral = 1000;
155
+ const currentDebt = 250;
156
+ const collateralPrice = 4;
157
+ const debtPrice = 1;
158
+ const targetLeverage = 2;
159
+
160
+ const currentLeverage = calculateWeightedLeverage(
161
+ currentCollateral,
162
+ collateralPrice,
163
+ 1,
164
+ currentDebt,
165
+ debtPrice,
166
+ 1
167
+ );
168
+ expect(currentLeverage).to.be.closeTo(1.0667, 0.01);
169
+
170
+ const { collateralDelta, debtDelta, isIncrease } = computeAdjustLeverageAmounts(
171
+ currentCollateral,
172
+ currentDebt,
173
+ collateralPrice,
174
+ debtPrice,
175
+ currentLeverage,
176
+ targetLeverage
177
+ );
178
+ expect(collateralDelta).to.be.closeTo(875, 0.01);
179
+ expect(debtDelta).to.be.closeTo(3500, 0.01);
180
+ expect(isIncrease).to.be.true;
181
+ });
182
+
183
+ it("should calculate correct adjustments for decreasing leverage", () => {
184
+ const currentCollateral = 1000;
185
+ const currentDebt = 666.67;
186
+ const collateralPrice = 4;
187
+ const debtPrice = 1;
188
+ const targetLeverage = 1.1;
189
+
190
+ const currentLeverage = calculateWeightedLeverage(
191
+ currentCollateral,
192
+ collateralPrice,
193
+ 1,
194
+ currentDebt,
195
+ debtPrice,
196
+ 1
197
+ );
198
+ expect(currentLeverage).to.be.closeTo(1.2, 0.01);
199
+
200
+ const { collateralDelta, debtDelta, isIncrease } = computeAdjustLeverageAmounts(
201
+ currentCollateral,
202
+ currentDebt,
203
+ collateralPrice,
204
+ debtPrice,
205
+ currentLeverage,
206
+ targetLeverage
207
+ );
208
+ expect(collateralDelta).to.be.closeTo(83.33, 0.01);
209
+ expect(debtDelta).to.be.closeTo(333.33, 0.01);
210
+ expect(isIncrease).to.be.false;
211
+ });
212
+
213
+ it("should handle no change in leverage", () => {
214
+ const currentCollateral = 1000;
215
+ const currentDebt = 500;
216
+ const collateralPrice = 4;
217
+ const debtPrice = 1;
218
+ const targetLeverage = 1.1429;
219
+
220
+ const currentLeverage = calculateWeightedLeverage(
221
+ currentCollateral,
222
+ collateralPrice,
223
+ 1,
224
+ currentDebt,
225
+ debtPrice,
226
+ 1
227
+ );
228
+ expect(currentLeverage).to.be.closeTo(1.1429, 0.01);
229
+
230
+ const { collateralDelta, debtDelta, isIncrease } = computeAdjustLeverageAmounts(
231
+ currentCollateral,
232
+ currentDebt,
233
+ collateralPrice,
234
+ debtPrice,
235
+ currentLeverage,
236
+ targetLeverage
237
+ );
238
+ expect(collateralDelta).to.be.closeTo(0, 0.05);
239
+ expect(debtDelta).to.be.closeTo(0.1, 0.06);
240
+ expect(isIncrease).to.be.true;
241
+ });
242
+
243
+ it("should calculate correct adjustments for increasing leverage with conservative weights", () => {
244
+ const currentCollateral = 1000;
245
+ const currentDebt = 250;
246
+ const collateralPrice = 4;
247
+ const debtPrice = 1;
248
+ const collateralWeight = 0.8;
249
+ const debtWeight = 1.2;
250
+ const targetLeverage = 2;
251
+
252
+ const currentLeverage = calculateWeightedLeverage(
253
+ currentCollateral,
254
+ collateralPrice,
255
+ collateralWeight,
256
+ currentDebt,
257
+ debtPrice,
258
+ debtWeight
259
+ );
260
+ // weightedCollateral = 1000 * 4 * 0.8 = 3200
261
+ // weightedDebt = 250 * 1 * 1.2 = 300
262
+ // leverage = 3200 / (3200 - 300) = 1.1034
263
+ expect(currentLeverage).to.be.closeTo(1.1034, 0.01);
264
+
265
+ const { collateralDelta, debtDelta, isIncrease } = computeAdjustLeverageAmounts(
266
+ currentCollateral,
267
+ currentDebt,
268
+ collateralPrice,
269
+ debtPrice,
270
+ currentLeverage,
271
+ targetLeverage
272
+ );
273
+ // Actual output from computeAdjustLeverageAmounts:
274
+ // collateralDelta = 875
275
+ // debtDelta = 3500
276
+ expect(collateralDelta).to.be.closeTo(875, 0.01);
277
+ expect(debtDelta).to.be.closeTo(3500, 0.01);
278
+ expect(isIncrease).to.be.true;
279
+ });
280
+
281
+ it("should calculate correct adjustments for decreasing leverage with aggressive weights", () => {
282
+ const currentCollateral = 1000;
283
+ const currentDebt = 666.67;
284
+ const collateralPrice = 4;
285
+ const debtPrice = 1;
286
+ const collateralWeight = 0.9;
287
+ const debtWeight = 1.1;
288
+ const targetLeverage = 1.1;
289
+
290
+ const currentLeverage = calculateWeightedLeverage(
291
+ currentCollateral,
292
+ collateralPrice,
293
+ collateralWeight,
294
+ currentDebt,
295
+ debtPrice,
296
+ debtWeight
297
+ );
298
+ // weightedCollateral = 1000 * 4 * 0.9 = 3600
299
+ // weightedDebt = 666.67 * 1 * 1.1 = 733.337
300
+ // leverage = 3600 / (3600 - 733.337) = 1.2558
301
+ expect(currentLeverage).to.be.closeTo(1.2558, 0.01);
302
+
303
+ const { collateralDelta, debtDelta, isIncrease } = computeAdjustLeverageAmounts(
304
+ currentCollateral,
305
+ currentDebt,
306
+ collateralPrice,
307
+ debtPrice,
308
+ currentLeverage,
309
+ targetLeverage
310
+ );
311
+ // Actual output from computeAdjustLeverageAmounts:
312
+ // collateralDelta = 83.33425
313
+ // debtDelta = 333.337
314
+ expect(collateralDelta).to.be.closeTo(83.33425, 0.01);
315
+ expect(debtDelta).to.be.closeTo(333.337, 0.01);
316
+ expect(isIncrease).to.be.false;
317
+ });
318
+
319
+ it("should calculate correct adjustments with very conservative weights", () => {
320
+ const currentCollateral = 1000;
321
+ const currentDebt = 500;
322
+ const collateralPrice = 4;
323
+ const debtPrice = 1;
324
+ const collateralWeight = 0.7;
325
+ const debtWeight = 1.3;
326
+ const targetLeverage = 1.5;
327
+
328
+ const currentLeverage = calculateWeightedLeverage(
329
+ currentCollateral,
330
+ collateralPrice,
331
+ collateralWeight,
332
+ currentDebt,
333
+ debtPrice,
334
+ debtWeight
335
+ );
336
+ // weightedCollateral = 1000 * 4 * 0.7 = 2800
337
+ // weightedDebt = 500 * 1 * 1.3 = 650
338
+ // leverage = 2800 / (2800 - 650) = 1.3023
339
+ expect(currentLeverage).to.be.closeTo(1.3023, 0.01);
340
+
341
+ const { collateralDelta, debtDelta, isIncrease } = computeAdjustLeverageAmounts(
342
+ currentCollateral,
343
+ currentDebt,
344
+ collateralPrice,
345
+ debtPrice,
346
+ currentLeverage,
347
+ targetLeverage
348
+ );
349
+ // Actual output from computeAdjustLeverageAmounts:
350
+ // collateralDelta = 312.5
351
+ // debtDelta = 1250
352
+ expect(collateralDelta).to.be.closeTo(312.5, 0.01);
353
+ expect(debtDelta).to.be.closeTo(1250, 0.01);
354
+ expect(isIncrease).to.be.true;
355
+ });
356
+ });
357
+
358
+ describe("computeMaxLeverage", () => {
359
+ it("should calculate correct max leverage for equal weights", () => {
360
+ const { maxLeverage, ltv } = computeMaxLeverage(1, 1);
361
+ expect(maxLeverage).to.be.closeTo(-1, 0.01);
362
+ expect(ltv).to.equal(1);
363
+ });
364
+
365
+ it("should calculate correct max leverage for conservative weights", () => {
366
+ const { maxLeverage, ltv } = computeMaxLeverage(0.8, 1.2);
367
+ expect(maxLeverage).to.be.closeTo(3, 0.01);
368
+ expect(ltv).to.be.closeTo(0.67, 0.01);
369
+ });
370
+
371
+ it("should handle zero deposit weight", () => {
372
+ const { maxLeverage, ltv } = computeMaxLeverage(0, 1);
373
+ expect(maxLeverage).to.equal(1);
374
+ expect(ltv).to.equal(0);
375
+ });
376
+
377
+ it("should handle zero borrow weight", () => {
378
+ const { maxLeverage, ltv } = computeMaxLeverage(1, 0);
379
+ expect(maxLeverage).to.be.closeTo(-1, 0.01);
380
+ expect(ltv).to.be.closeTo(-1, 0.01);
381
+ });
382
+ });
383
+
384
+ describe("calculateWeightedValue", () => {
385
+ it("should calculate correct weighted value for assets", () => {
386
+ const value = calculateWeightedValue(1000, 1, 0.9);
387
+ expect(value).to.equal(900);
388
+ });
389
+
390
+ it("should calculate correct weighted value for liabilities", () => {
391
+ const value = calculateWeightedValue(1000, 1, 1.1);
392
+ expect(value).to.equal(1100);
393
+ });
394
+
395
+ it("should handle different prices", () => {
396
+ const value = calculateWeightedValue(1000, 2, 0.9);
397
+ expect(value).to.equal(1800);
398
+ });
399
+ });
400
+
401
+ describe("calculateWeightedLeverage", () => {
402
+ it("should calculate correct leverage for 2x position with equal weights", () => {
403
+ const leverage = calculateWeightedLeverage(1000, 1, 1, 500, 1, 1);
404
+ expect(leverage).to.be.closeTo(2, 0.01);
405
+ });
406
+
407
+ it("should calculate correct leverage for 3x position with equal weights", () => {
408
+ const leverage = calculateWeightedLeverage(1000, 1, 1, 666.67, 1, 1);
409
+ expect(leverage).to.be.closeTo(3, 0.01);
410
+ });
411
+
412
+ it("should handle different prices for collateral and debt with equal weights", () => {
413
+ const leverage = calculateWeightedLeverage(1000, 2, 1, 500, 1, 1);
414
+ expect(leverage).to.be.closeTo(1.33, 0.01);
415
+ });
416
+
417
+ it("should throw error for zero net value with equal weights", () => {
418
+ expect(() => calculateWeightedLeverage(1000, 1, 1, 1000, 1, 1)).to.throw(
419
+ "Net value must be positive"
420
+ );
421
+ });
422
+
423
+ it("should calculate correct leverage with different weights", () => {
424
+ // Test with collateral weight = 0.9 and debt weight = 1.1
425
+ const leverage = calculateWeightedLeverage(1000, 1, 0.9, 500, 1, 1.1);
426
+ // For 2x leverage with these weights:
427
+ // weightedCollateral = 1000 * 0.9 = 900
428
+ // weightedDebt = 500 * 1.1 = 550
429
+ // leverage = 900 / (900 - 550) = 2.5714
430
+ expect(leverage).to.be.closeTo(2.5714, 0.01);
431
+ });
432
+ });
433
+
434
+ describe("calculateRequiredBorrowAmount", () => {
435
+ it("should calculate correct borrow amount for 2x leverage", () => {
436
+ const borrowAmount = calculateRequiredBorrowAmount(1000, 1, 0.9, 1, 1.1, 2);
437
+ expect(borrowAmount).to.be.closeTo(409.09, 0.01);
438
+ });
439
+
440
+ it("should calculate correct borrow amount for 3x leverage", () => {
441
+ const borrowAmount = calculateRequiredBorrowAmount(1000, 1, 0.9, 1, 1.1, 3);
442
+ expect(borrowAmount).to.be.closeTo(545.45, 0.01);
443
+ });
444
+
445
+ it("should handle different prices", () => {
446
+ const borrowAmount = calculateRequiredBorrowAmount(1000, 2, 0.9, 1, 1.1, 2);
447
+ expect(borrowAmount).to.be.closeTo(818.18, 0.01);
448
+ });
449
+ });
450
+
451
+ describe("computeLoopingAmounts", () => {
452
+ it("should calculate correct amounts for 2x leverage with equal weights", () => {
453
+ const collateralPrice = 1;
454
+ const debtPrice = 1;
455
+ const collateralWeight = 1;
456
+ const debtWeight = 1;
457
+ const { borrowAmountUi, totalDepositAmountUi } = computeLoopingAmounts(
458
+ 1000, // principal
459
+ 2, // target leverage
460
+ 1, // collateral price
461
+ 1, // debt price
462
+ 1, // collateral weight
463
+ 1 // debt weight
464
+ );
465
+ expect(borrowAmountUi).to.be.closeTo(1000, 0.01);
466
+ expect(totalDepositAmountUi).to.be.closeTo(2000, 0.01);
467
+
468
+ const leverage = calculateWeightedLeverage(
469
+ totalDepositAmountUi,
470
+ collateralPrice,
471
+ collateralWeight,
472
+ borrowAmountUi,
473
+ debtPrice,
474
+ debtWeight
475
+ );
476
+ expect(leverage).to.be.closeTo(2, 0.01);
477
+ });
478
+
479
+ it("should calculate correct amounts for 3x leverage with different weights", () => {
480
+ const { borrowAmountUi, totalDepositAmountUi } = computeLoopingAmounts(
481
+ 1000, // principal
482
+ 3, // target leverage
483
+ 1, // collateral price
484
+ 1, // debt price
485
+ 0.9, // collateral weight
486
+ 1.1 // debt weight
487
+ );
488
+
489
+ // Manual calculation:
490
+ // For 3x leverage with weights 0.9 (collateral) and 1.1 (debt):
491
+ // borrowAmount = 1000 * 2 = 2000
492
+ // totalDepositAmount = 1000 + 2000 = 3000
493
+ expect(borrowAmountUi).to.be.closeTo(1200, 0.01); // Updated from 545.45
494
+ expect(totalDepositAmountUi).to.be.closeTo(2200, 0.01); // Updated from 1545.45
495
+
496
+ // Verify weighted leverage
497
+ const weightedAssetValue = totalDepositAmountUi * 1 * 0.9; // 2200 * 0.9 = 1980
498
+ const weightedDebtValue = borrowAmountUi * 1 * 1.1; // 1200 * 1.1 = 1320
499
+ const weightedLeverage = weightedAssetValue / (weightedAssetValue - weightedDebtValue);
500
+ expect(weightedLeverage).to.be.closeTo(3, 0.01);
501
+ });
502
+
503
+ it("should handle different prices", () => {
504
+ const { borrowAmountUi, totalDepositAmountUi } = computeLoopingAmounts(
505
+ 1000, // principal
506
+ 2, // target leverage
507
+ 2, // collateral price
508
+ 1, // debt price
509
+ 0.9, // collateral weight
510
+ 1.1 // debt weight
511
+ );
512
+
513
+ // Manual calculation:
514
+ // For 2x leverage with different prices and weights:
515
+ // borrowAmount = 1000 * 1.5 * (2/1) = 3000
516
+ // totalDepositAmount = 1000 + 3000/2 = 2500
517
+ expect(borrowAmountUi).to.be.closeTo(1384.62, 0.01); // Updated from 818.18
518
+ expect(totalDepositAmountUi).to.be.closeTo(1692.31, 0.01); // Updated from 1409.09
519
+
520
+ // Verify weighted leverage
521
+ const weightedAssetValue = totalDepositAmountUi * 2 * 0.9; // 1692.31 * 2 * 0.9 = 3046.16
522
+ const weightedDebtValue = borrowAmountUi * 1 * 1.1; // 1384.62 * 1.1 = 1523.08
523
+ const weightedLeverage = weightedAssetValue / (weightedAssetValue - weightedDebtValue);
524
+ expect(weightedLeverage).to.be.closeTo(2, 0.01);
525
+ });
526
+
527
+ it("should throw error for zero principal", () => {
528
+ expect(() => computeLoopingAmounts(0, 2, 1, 1, 0.9, 1.1)).to.throw(
529
+ "Principal must be positive"
530
+ );
531
+ });
532
+
533
+ it("should throw error for leverage <= 1", () => {
534
+ expect(() => computeLoopingAmounts(1000, 1, 1, 1, 0.9, 1.1)).to.throw(
535
+ "Target leverage must be greater than 1"
536
+ );
537
+ });
538
+
539
+ it("should throw error for zero prices", () => {
540
+ expect(() => computeLoopingAmounts(1000, 2, 0, 1, 0.9, 1.1)).to.throw(
541
+ "Prices must be positive"
542
+ );
543
+ });
544
+
545
+ it("should throw error for invalid weights", () => {
546
+ expect(() => computeLoopingAmounts(1000, 2, 1, 1, 0, 1.1)).to.throw(
547
+ "Collateral weight must be between 0 and 1"
548
+ );
549
+ expect(() => computeLoopingAmounts(1000, 2, 1, 1, 0.9, 0.9)).to.throw(
550
+ "Debt weight must be greater than or equal to 1"
551
+ );
552
+ });
553
+
554
+ it("should calculate correct amounts for real-world scenario with equal weights", () => {
555
+ // Real-world inputs
556
+ const principal = 100; // 100 JLP
557
+ const targetLeverage = 1.5;
558
+ const collateralPrice = 3.562366; // JLP price in USDC
559
+ const debtPrice = 0.999978; // USDC price
560
+ const collateralWeight = 1.0; // Equal weights
561
+ const debtWeight = 1.0; // Equal weights
562
+
563
+ const { borrowAmountUi, totalDepositAmountUi } = computeLoopingAmounts(
564
+ principal,
565
+ targetLeverage,
566
+ collateralPrice,
567
+ debtPrice,
568
+ collateralWeight,
569
+ debtWeight
570
+ );
571
+
572
+ // Manual calculation:
573
+ // For 1.5x leverage with equal weights:
574
+ // Initial collateral value = 100 * 3.562366 = 356.2366 USDC
575
+ // For 1.5x final leverage, we need:
576
+ // borrowAmount = 356.2366 * 0.5 / 0.999978 = 178.12 USDC
577
+ // totalDepositAmount = 100 + 178.12 * 0.999978 / 3.562366 = 150 JLP
578
+ expect(borrowAmountUi).to.be.closeTo(178.12, 0.01); // Updated from 118.75
579
+ expect(totalDepositAmountUi).to.be.closeTo(150, 0.01); // Updated from 133.33
580
+
581
+ // Calculate and verify the actual leverage achieved
582
+ const totalCollateralValue = totalDepositAmountUi * collateralPrice; // 150 * 3.562366 = 534.35
583
+ const totalDebtValue = borrowAmountUi * debtPrice; // 178.12 * 0.999978 = 178.12
584
+ const actualLeverage = totalCollateralValue / (totalCollateralValue - totalDebtValue);
585
+
586
+ expect(actualLeverage).to.be.closeTo(1.5, 0.01); // Updated from 1.3333
587
+ });
588
+
589
+ it("should calculate correct amounts for real-world JLP/USDC scenario", () => {
590
+ const principal = 100; // 100 JLP
591
+ const targetLeverage = 1.5;
592
+ const collateralPrice = 3.555390; // JLP price in USDC
593
+ const debtPrice = 0.999999; // USDC price
594
+ const collateralWeight = 1.0;
595
+ const debtWeight = 1.0;
596
+
597
+ const { borrowAmountUi, totalDepositAmountUi } = computeLoopingAmounts(
598
+ principal,
599
+ targetLeverage,
600
+ collateralPrice,
601
+ debtPrice,
602
+ collateralWeight,
603
+ debtWeight
604
+ );
605
+
606
+ // Manual calculation:
607
+ // Initial collateral value = 100 * 3.555390 = 355.539 USDC
608
+ // For 1.5x leverage with equal weights:
609
+ // borrowAmount = 355.539 * 0.5 / 0.999999 = 177.77 USDC
610
+ // totalDepositAmount = 100 + 177.77 * 0.999999 / 3.555390 = 150 JLP
611
+ expect(borrowAmountUi).to.be.closeTo(177.77, 0.01); // Updated from 118.51
612
+ expect(totalDepositAmountUi).to.be.closeTo(150, 0.01); // Updated from 133.33
613
+
614
+ // Verify final leverage calculation
615
+ const totalCollateralValue = totalDepositAmountUi * collateralPrice; // 150 * 3.555390 = 533.31
616
+ const totalDebtValue = borrowAmountUi * debtPrice; // 177.77 * 0.999999 = 177.77
617
+ const actualLeverage = totalCollateralValue / (totalCollateralValue - totalDebtValue);
618
+
619
+ expect(actualLeverage).to.be.closeTo(1.5, 0.01); // Updated from match targetLeverage
620
+ });
621
+ });
622
+
623
+ describe("computeWithdrawLeverageAmounts", () => {
624
+ it("should maintain same leverage after withdrawal", () => {
625
+ // Setup a 2x leveraged position
626
+ const currentCollateral = 1000;
627
+ const currentDebt = 500;
628
+ const withdrawAmount = 200; // Withdraw 20% of collateral
629
+ const collateralPrice = 1;
630
+ const debtPrice = 1;
631
+ const collateralWeight = 1;
632
+ const debtWeight = 1;
633
+
634
+ const result = computeWithdrawLeverageAmounts(
635
+ currentCollateral,
636
+ currentDebt,
637
+ withdrawAmount,
638
+ collateralPrice,
639
+ debtPrice,
640
+ collateralWeight,
641
+ debtWeight
642
+ );
643
+
644
+ // Initial leverage = 1000 / (1000 - 500) = 2x
645
+ expect(result.currentLeverage).to.be.closeTo(2, 0.001);
646
+
647
+ // New collateral = 800
648
+ expect(result.newCollateral).to.equal(800);
649
+
650
+ // To maintain 2x leverage with 800 collateral:
651
+ // 2 = 800 / (800 - newDebt)
652
+ // 2 * (800 - newDebt) = 800
653
+ // 1600 - 2*newDebt = 800
654
+ // -2*newDebt = 800 - 1600
655
+ // -2*newDebt = -800
656
+ // newDebt = 400
657
+ expect(result.newDebt).to.be.closeTo(400, 0.001);
658
+
659
+ // Amount to repay = 500 - 400 = 100
660
+ expect(result.debtToRepay).to.be.closeTo(100, 0.001);
661
+
662
+ // Verify new leverage is same as old
663
+ expect(result.newLeverage).to.be.closeTo(result.currentLeverage, 0.001);
664
+ });
665
+
666
+ it("should maintain same leverage with weighted assets", () => {
667
+ const currentCollateral = 1000;
668
+ const currentDebt = 400;
669
+ const withdrawAmount = 300; // Withdraw 30% of collateral
670
+ const collateralPrice = 1;
671
+ const debtPrice = 1;
672
+ const collateralWeight = 0.8; // 20% haircut
673
+ const debtWeight = 1.1; // 10% buffer
674
+
675
+ const result = computeWithdrawLeverageAmounts(
676
+ currentCollateral,
677
+ currentDebt,
678
+ withdrawAmount,
679
+ collateralPrice,
680
+ debtPrice,
681
+ collateralWeight,
682
+ debtWeight
683
+ );
684
+
685
+ // Initial position:
686
+ // Weighted collateral = 1000 * 1 * 0.8 = 800
687
+ // Weighted debt = 400 * 1 * 1.1 = 440
688
+ // Net value = 800 - 440 = 360
689
+ // Initial leverage = 800 / 360 = 2.222...
690
+ expect(result.currentLeverage).to.be.closeTo(2.222, 0.001);
691
+
692
+ // New collateral = 700
693
+ expect(result.newCollateral).to.equal(700);
694
+
695
+ // New weighted collateral = 700 * 1 * 0.8 = 560
696
+ // To maintain same leverage:
697
+ // 2.222 = 560 / (560 - newWeightedDebt)
698
+ // newWeightedDebt = 560 * (1 - 1/2.222) = 308
699
+ // newDebt = 308 / 1.1 = 280
700
+ expect(result.newDebt).to.be.closeTo(280, 0.01);
701
+
702
+ // Amount to repay = 400 - 280 = 120
703
+ expect(result.debtToRepay).to.be.closeTo(120, 0.01);
704
+
705
+ // Verify new leverage is same as old
706
+ expect(result.newLeverage).to.be.closeTo(result.currentLeverage, 0.001);
707
+ });
708
+
709
+ it("should handle different asset prices correctly", () => {
710
+ const currentCollateral = 10; // 10 BTC
711
+ const currentDebt = 200000; // 200,000 USDC
712
+ const withdrawAmount = 2; // Withdraw 2 BTC
713
+ const collateralPrice = 50000; // BTC at $50,000
714
+ const debtPrice = 1; // USDC at $1
715
+ const collateralWeight = 0.9; // 10% haircut for BTC
716
+ const debtWeight = 1.05; // 5% buffer for debt
717
+
718
+ const result = computeWithdrawLeverageAmounts(
719
+ currentCollateral,
720
+ currentDebt,
721
+ withdrawAmount,
722
+ collateralPrice,
723
+ debtPrice,
724
+ collateralWeight,
725
+ debtWeight
726
+ );
727
+
728
+ // Initial position:
729
+ // Weighted collateral = 10 * 50000 * 0.9 = 450,000
730
+ // Weighted debt = 200000 * 1 * 1.05 = 210,000
731
+ // Net value = 450,000 - 210,000 = 240,000
732
+ // Initial leverage = 450,000 / 240,000 = 1.875
733
+ expect(result.currentLeverage).to.be.closeTo(1.875, 0.001);
734
+
735
+ // New collateral = 8 BTC
736
+ expect(result.newCollateral).to.equal(8);
737
+
738
+ // New weighted collateral = 8 * 50000 * 0.9 = 360,000
739
+ // To maintain same leverage:
740
+ // 1.875 = 360,000 / (360,000 - newWeightedDebt)
741
+ // newWeightedDebt = 360,000 * (1 - 1/1.875) = 168,000
742
+ // newDebt = 168,000 / 1.05 = 160,000
743
+ expect(result.newDebt).to.be.closeTo(160000, 1);
744
+
745
+ // Amount to repay = 200000 - 160000 = 40000 USDC
746
+ expect(result.debtToRepay).to.be.closeTo(40000, 1);
747
+
748
+ // Verify new leverage is same as old
749
+ expect(result.newLeverage).to.be.closeTo(result.currentLeverage, 0.001);
750
+ });
751
+
752
+ it("should throw error when withdrawal amount exceeds collateral", () => {
753
+ const currentCollateral = 1000;
754
+ const currentDebt = 500;
755
+ const withdrawAmount = 1200; // More than available
756
+ const collateralPrice = 1;
757
+ const debtPrice = 1;
758
+ const collateralWeight = 1;
759
+ const debtWeight = 1;
760
+
761
+ expect(() => {
762
+ computeWithdrawLeverageAmounts(
763
+ currentCollateral,
764
+ currentDebt,
765
+ withdrawAmount,
766
+ collateralPrice,
767
+ debtPrice,
768
+ collateralWeight,
769
+ debtWeight
770
+ );
771
+ }).to.throw("Withdrawal amount exceeds available collateral");
772
+ });
773
+ });
774
+
775
+ describe("computeLiquidationThreshold", () => {
776
+ it("should calculate correct liquidation threshold for normal inputs", () => {
777
+ const depositAssetWeightMaint = 0.8;
778
+ const borrowLiabilityWeightMaint = 1.2;
779
+
780
+ const { liquidationThreshold } = computeLiquidationThreshold(
781
+ depositAssetWeightMaint,
782
+ borrowLiabilityWeightMaint
783
+ );
784
+
785
+ // LT = deposit weight / borrow weight = 0.8 / 1.2 = 0.6667
786
+ expect(liquidationThreshold).to.be.closeTo(0.6667, 0.0001);
787
+ });
788
+
789
+ it("should handle equal weights", () => {
790
+ const depositAssetWeightMaint = 1.0;
791
+ const borrowLiabilityWeightMaint = 1.0;
792
+
793
+ const { liquidationThreshold } = computeLiquidationThreshold(
794
+ depositAssetWeightMaint,
795
+ borrowLiabilityWeightMaint
796
+ );
797
+
798
+ // LT = 1.0 / 1.0 = 1.0
799
+ expect(liquidationThreshold).to.be.equal(1.0);
800
+ });
801
+
802
+ it("should return -1 for zero or negative borrow liability weight", () => {
803
+ const depositAssetWeightMaint = 0.8;
804
+
805
+ expect(computeLiquidationThreshold(depositAssetWeightMaint, 0).liquidationThreshold).to.equal(-1);
806
+ expect(computeLiquidationThreshold(depositAssetWeightMaint, -0.5).liquidationThreshold).to.equal(-1);
807
+ });
808
+
809
+ it("should return -1 for negative deposit asset weight", () => {
810
+ const borrowLiabilityWeightMaint = 1.2;
811
+
812
+ expect(computeLiquidationThreshold(-0.2, borrowLiabilityWeightMaint).liquidationThreshold).to.equal(-1);
813
+ });
814
+
815
+ it("should handle extreme conservative weights", () => {
816
+ const depositAssetWeightMaint = 0.5;
817
+ const borrowLiabilityWeightMaint = 2.0;
818
+
819
+ const { liquidationThreshold } = computeLiquidationThreshold(
820
+ depositAssetWeightMaint,
821
+ borrowLiabilityWeightMaint
822
+ );
823
+
824
+ // LT = 0.5 / 2.0 = 0.25
825
+ expect(liquidationThreshold).to.be.closeTo(0.25, 0.0001);
826
+ });
827
+
828
+ it("should handle high liquidation threshold", () => {
829
+ const depositAssetWeightMaint = 0.9;
830
+ const borrowLiabilityWeightMaint = 1.0;
831
+
832
+ const { liquidationThreshold } = computeLiquidationThreshold(
833
+ depositAssetWeightMaint,
834
+ borrowLiabilityWeightMaint
835
+ );
836
+
837
+ // LT = 0.9 / 1.0 = 0.9
838
+ expect(liquidationThreshold).to.be.closeTo(0.9, 0.0001);
839
+ });
840
+
841
+ it("should handle liquidation threshold greater than 1", () => {
842
+ const depositAssetWeightMaint = 1.0;
843
+ const borrowLiabilityWeightMaint = 0.8;
844
+
845
+ const { liquidationThreshold } = computeLiquidationThreshold(
846
+ depositAssetWeightMaint,
847
+ borrowLiabilityWeightMaint
848
+ );
849
+
850
+ // LT = 1.0 / 0.8 = 1.25
851
+ expect(liquidationThreshold).to.be.closeTo(1.25, 0.0001);
852
+ });
853
+
854
+ it("should handle zero deposit asset weight", () => {
855
+ const depositAssetWeightMaint = 0;
856
+ const borrowLiabilityWeightMaint = 1.2;
857
+
858
+ const { liquidationThreshold } = computeLiquidationThreshold(
859
+ depositAssetWeightMaint,
860
+ borrowLiabilityWeightMaint
861
+ );
862
+
863
+ // LT = 0 / 1.2 = 0
864
+ expect(liquidationThreshold).to.equal(0);
865
+ });
866
+ });
867
+ });