@indigo-labs/indigo-sdk 0.2.1 → 0.2.4

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,1495 @@
1
+ import { beforeEach, expect, test, vi } from 'vitest';
2
+ import {
3
+ addrDetails,
4
+ cdpCollateralRatioPercentage,
5
+ fromSystemParamsAsset,
6
+ getInlineDatumOrThrow,
7
+ LRPDatum,
8
+ LrpParamsSP,
9
+ openLrp,
10
+ parseInterestOracleDatum,
11
+ parsePriceOracleDatum,
12
+ SystemParams,
13
+ } from '../src';
14
+ import {
15
+ addAssets,
16
+ Emulator,
17
+ EmulatorAccount,
18
+ fromText,
19
+ generateEmulatorAccount,
20
+ Lucid,
21
+ toText,
22
+ UTxO,
23
+ } from '@lucid-evolution/lucid';
24
+ import { findAllNecessaryOrefs, findCdp } from './queries/cdp-queries';
25
+ import { LucidContext, runAndAwaitTx } from './test-helpers';
26
+ import { describe } from 'vitest';
27
+ import {
28
+ assetClassValueOf,
29
+ lovelacesAmt,
30
+ mkLovelacesOf,
31
+ } from '../src/utils/value-helpers';
32
+ import { init } from './endpoints/initialize';
33
+ import { iusdInitialAssetCfg } from './mock/assets-mock';
34
+ import { findAllLrps } from './queries/lrp-queries';
35
+ import { ocdFloor, OnChainDecimal } from '../src/types/on-chain-decimal';
36
+ import { assertValueInRange } from './utils/asserts';
37
+
38
+ import {
39
+ calculateLeverageFromCollateralRatio,
40
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
41
+ } from '../src/contracts/leverage/helpers';
42
+ import { leverageCdpWithLrp } from '../src/contracts/leverage/transactions';
43
+ import {
44
+ calculateTotalAdaForRedemption,
45
+ lrpRedeemableLovelacesInclReimb,
46
+ MIN_LRP_COLLATERAL_AMT,
47
+ randomLrpsSubsetSatisfyingTargetLovelaces,
48
+ } from '../src/contracts/lrp/helpers';
49
+
50
+ type MyContext = LucidContext<{
51
+ admin: EmulatorAccount;
52
+ user: EmulatorAccount;
53
+ }>;
54
+
55
+ async function openLrps(
56
+ context: MyContext,
57
+ sysParams: SystemParams,
58
+ iasset: string,
59
+ amountsToSpend: bigint[],
60
+ maxPrice: OnChainDecimal,
61
+ ): Promise<void> {
62
+ for (const amt of amountsToSpend) {
63
+ await runAndAwaitTx(
64
+ context.lucid,
65
+ openLrp(iasset, amt, maxPrice, context.lucid, sysParams),
66
+ );
67
+ }
68
+ }
69
+
70
+ function hadLrpRedemption(
71
+ lrp: { utxo: UTxO; datum: LRPDatum },
72
+ lrpParams: LrpParamsSP,
73
+ ): boolean {
74
+ return (
75
+ assetClassValueOf(lrp.utxo.assets, {
76
+ currencySymbol: lrpParams.iassetPolicyId.unCurrencySymbol,
77
+ tokenName: lrp.datum.iasset,
78
+ }) > 0
79
+ );
80
+ }
81
+
82
+ describe('randomLrpsSubsetSatisfyingTargetLovelaces', () => {
83
+ const mockUtxo = (ada: bigint): UTxO => ({
84
+ address: '',
85
+ assets: mkLovelacesOf(ada),
86
+ outputIndex: 0,
87
+ txHash: '',
88
+ });
89
+
90
+ const mockLrpParams: LrpParamsSP = {
91
+ iassetNft: [{ unCurrencySymbol: '' }, { unTokenName: '' }],
92
+ iassetPolicyId: { unCurrencySymbol: '' },
93
+ minRedemptionLovelacesAmt: 10n,
94
+ versionRecordToken: [{ unCurrencySymbol: '' }, { unTokenName: '' }],
95
+ };
96
+
97
+ test('1', () => {
98
+ const lrps: [UTxO, LRPDatum][] = [
99
+ [
100
+ mockUtxo(100n),
101
+ {
102
+ iasset: 'iUSD',
103
+ lovelacesToSpend: 100n,
104
+ maxPrice: { getOnChainInt: 1_000_000n },
105
+ owner: '',
106
+ },
107
+ ],
108
+ [
109
+ mockUtxo(100n),
110
+ {
111
+ iasset: 'iUSD',
112
+ lovelacesToSpend: 100n,
113
+ maxPrice: { getOnChainInt: 1_000_000n },
114
+ owner: '',
115
+ },
116
+ ],
117
+ ];
118
+
119
+ expect(
120
+ randomLrpsSubsetSatisfyingTargetLovelaces(
121
+ 'iUSD',
122
+ 120n,
123
+ { getOnChainInt: 1_000_000n },
124
+ lrps,
125
+ mockLrpParams,
126
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
127
+ ),
128
+ ).toEqual(expect.arrayContaining(lrps));
129
+ });
130
+
131
+ test('2', () => {
132
+ const lrps: [UTxO, LRPDatum][] = [
133
+ [
134
+ mockUtxo(100n),
135
+ {
136
+ iasset: 'iUSD',
137
+ lovelacesToSpend: 100n,
138
+ maxPrice: { getOnChainInt: 1_000_000n },
139
+ owner: '',
140
+ },
141
+ ],
142
+ ];
143
+
144
+ expect(
145
+ randomLrpsSubsetSatisfyingTargetLovelaces(
146
+ 'iUSD',
147
+ 100n,
148
+ { getOnChainInt: 1_000_000n },
149
+ lrps,
150
+ mockLrpParams,
151
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
152
+ ),
153
+ ).toEqual(lrps);
154
+ });
155
+
156
+ test('filtering by iasset 1', () => {
157
+ const lrps: [UTxO, LRPDatum][] = [
158
+ [
159
+ mockUtxo(100n),
160
+ {
161
+ iasset: 'iUSD',
162
+ lovelacesToSpend: 100n,
163
+ maxPrice: { getOnChainInt: 1_000_000n },
164
+ owner: '',
165
+ },
166
+ ],
167
+ [
168
+ mockUtxo(100n),
169
+ {
170
+ iasset: 'iBTC',
171
+ lovelacesToSpend: 100n,
172
+ maxPrice: { getOnChainInt: 1_000_000n },
173
+ owner: '',
174
+ },
175
+ ],
176
+ [
177
+ mockUtxo(100n),
178
+ {
179
+ iasset: 'iUSD',
180
+ lovelacesToSpend: 100n,
181
+ maxPrice: { getOnChainInt: 1_000_000n },
182
+ owner: '',
183
+ },
184
+ ],
185
+ ];
186
+
187
+ expect(
188
+ randomLrpsSubsetSatisfyingTargetLovelaces(
189
+ 'iUSD',
190
+ 110n,
191
+ { getOnChainInt: 1_000_000n },
192
+ lrps,
193
+ mockLrpParams,
194
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
195
+ ),
196
+ ).toEqual(expect.arrayContaining([lrps[0], lrps[2]]));
197
+ });
198
+
199
+ test('filtering by iasset 2', () => {
200
+ const lrps: [UTxO, LRPDatum][] = [
201
+ [
202
+ mockUtxo(100n),
203
+ {
204
+ iasset: 'iUSD',
205
+ lovelacesToSpend: 100n,
206
+ maxPrice: { getOnChainInt: 1_000_000n },
207
+ owner: '',
208
+ },
209
+ ],
210
+ [
211
+ mockUtxo(100n),
212
+ {
213
+ iasset: 'iBTC',
214
+ lovelacesToSpend: 100n,
215
+ maxPrice: { getOnChainInt: 1_000_000n },
216
+ owner: '',
217
+ },
218
+ ],
219
+ ];
220
+
221
+ expect(() =>
222
+ randomLrpsSubsetSatisfyingTargetLovelaces(
223
+ 'iUSD',
224
+ 110n,
225
+ { getOnChainInt: 1_000_000n },
226
+ lrps,
227
+ mockLrpParams,
228
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
229
+ ),
230
+ ).toThrowError("Couldn't achieve target lovelaces");
231
+ });
232
+
233
+ test('filtering by price 1', () => {
234
+ const lrps: [UTxO, LRPDatum][] = [
235
+ [
236
+ mockUtxo(100n),
237
+ {
238
+ iasset: 'iUSD',
239
+ lovelacesToSpend: 100n,
240
+ maxPrice: { getOnChainInt: 1_500_000n },
241
+ owner: '',
242
+ },
243
+ ],
244
+ [
245
+ mockUtxo(100n),
246
+ {
247
+ iasset: 'iUSD',
248
+ lovelacesToSpend: 100n,
249
+ maxPrice: { getOnChainInt: 1_000_000n },
250
+ owner: '',
251
+ },
252
+ ],
253
+ ];
254
+
255
+ expect(() =>
256
+ randomLrpsSubsetSatisfyingTargetLovelaces(
257
+ 'iUSD',
258
+ 120n,
259
+ { getOnChainInt: 1_100_000n },
260
+ lrps,
261
+ mockLrpParams,
262
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
263
+ ),
264
+ ).toThrowError("Couldn't achieve target lovelaces");
265
+ });
266
+
267
+ test('filtering by price 2', () => {
268
+ const lrps: [UTxO, LRPDatum][] = [
269
+ [
270
+ mockUtxo(100n),
271
+ {
272
+ iasset: 'iUSD',
273
+ lovelacesToSpend: 50n,
274
+ maxPrice: { getOnChainInt: 1_500_000n },
275
+ owner: '',
276
+ },
277
+ ],
278
+ [
279
+ mockUtxo(100n),
280
+ {
281
+ iasset: 'iUSD',
282
+ lovelacesToSpend: 100n,
283
+ maxPrice: { getOnChainInt: 1_000_000n },
284
+ owner: '',
285
+ },
286
+ ],
287
+ [
288
+ mockUtxo(100n),
289
+ {
290
+ iasset: 'iUSD',
291
+ lovelacesToSpend: 100n,
292
+ maxPrice: { getOnChainInt: 1_300_000n },
293
+ owner: '',
294
+ },
295
+ ],
296
+ ];
297
+
298
+ expect(
299
+ randomLrpsSubsetSatisfyingTargetLovelaces(
300
+ 'iUSD',
301
+ 120n,
302
+ { getOnChainInt: 1_100_000n },
303
+ lrps,
304
+ mockLrpParams,
305
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
306
+ ),
307
+ ).toEqual(expect.arrayContaining([lrps[0], lrps[2]]));
308
+ });
309
+
310
+ test('min redemption check 1', () => {
311
+ const lrps: [UTxO, LRPDatum][] = [
312
+ [
313
+ mockUtxo(100n),
314
+ {
315
+ iasset: 'iUSD',
316
+ lovelacesToSpend: 100n,
317
+ maxPrice: { getOnChainInt: 1_000_000n },
318
+ owner: '',
319
+ },
320
+ ],
321
+ [
322
+ mockUtxo(100n),
323
+ {
324
+ iasset: 'iUSD',
325
+ lovelacesToSpend: 100n,
326
+ maxPrice: { getOnChainInt: 1_000_000n },
327
+ owner: '',
328
+ },
329
+ ],
330
+ ];
331
+
332
+ const mockedShuffle = vi.fn().mockImplementation(() => lrps);
333
+
334
+ expect(
335
+ randomLrpsSubsetSatisfyingTargetLovelaces(
336
+ 'iUSD',
337
+ 105n,
338
+ { getOnChainInt: 1_000_000n },
339
+ lrps,
340
+ mockLrpParams,
341
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
342
+ mockedShuffle,
343
+ ),
344
+ ).toEqual([lrps[0]]);
345
+ });
346
+
347
+ test('min redemption check 2', () => {
348
+ const lrps: [UTxO, LRPDatum][] = [
349
+ [
350
+ mockUtxo(100n),
351
+ {
352
+ iasset: 'iUSD',
353
+ lovelacesToSpend: 100n,
354
+ maxPrice: { getOnChainInt: 1_000_000n },
355
+ owner: '',
356
+ },
357
+ ],
358
+ [
359
+ mockUtxo(5n),
360
+ {
361
+ iasset: 'iUSD',
362
+ lovelacesToSpend: 5n,
363
+ maxPrice: { getOnChainInt: 1_000_000n },
364
+ owner: '',
365
+ },
366
+ ],
367
+ [
368
+ mockUtxo(100n),
369
+ {
370
+ iasset: 'iUSD',
371
+ lovelacesToSpend: 100n,
372
+ maxPrice: { getOnChainInt: 1_000_000n },
373
+ owner: '',
374
+ },
375
+ ],
376
+ ];
377
+
378
+ const mockedShuffle = vi.fn().mockImplementation(() => lrps);
379
+
380
+ expect(
381
+ randomLrpsSubsetSatisfyingTargetLovelaces(
382
+ 'iUSD',
383
+ 120n,
384
+ { getOnChainInt: 1_000_000n },
385
+ lrps,
386
+ mockLrpParams,
387
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
388
+ mockedShuffle,
389
+ ),
390
+ ).toEqual([lrps[0], lrps[2]]);
391
+ });
392
+
393
+ test('min redemption check 3', () => {
394
+ const lrps: [UTxO, LRPDatum][] = [
395
+ [
396
+ mockUtxo(100n),
397
+ {
398
+ iasset: 'iUSD',
399
+ lovelacesToSpend: 100n,
400
+ maxPrice: { getOnChainInt: 1_000_000n },
401
+ owner: '',
402
+ },
403
+ ],
404
+ [
405
+ mockUtxo(15n),
406
+ {
407
+ iasset: 'iUSD',
408
+ lovelacesToSpend: 15n,
409
+ maxPrice: { getOnChainInt: 1_000_000n },
410
+ owner: '',
411
+ },
412
+ ],
413
+ [
414
+ mockUtxo(100n),
415
+ {
416
+ iasset: 'iUSD',
417
+ lovelacesToSpend: 100n,
418
+ maxPrice: { getOnChainInt: 1_000_000n },
419
+ owner: '',
420
+ },
421
+ ],
422
+ ];
423
+
424
+ const mockedShuffle = vi.fn().mockImplementation(() => lrps);
425
+
426
+ expect(
427
+ randomLrpsSubsetSatisfyingTargetLovelaces(
428
+ 'iUSD',
429
+ 120n,
430
+ { getOnChainInt: 1_000_000n },
431
+ lrps,
432
+ mockLrpParams,
433
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
434
+ mockedShuffle,
435
+ ),
436
+ ).toEqual([lrps[0], lrps[2]]);
437
+ });
438
+
439
+ test('max redemptions check 1', () => {
440
+ const lrps: [UTxO, LRPDatum][] = [
441
+ [
442
+ mockUtxo(100n),
443
+ {
444
+ iasset: 'iUSD',
445
+ lovelacesToSpend: 100n,
446
+ maxPrice: { getOnChainInt: 1_000_000n },
447
+ owner: '',
448
+ },
449
+ ],
450
+ [
451
+ mockUtxo(90n),
452
+ {
453
+ iasset: 'iUSD',
454
+ lovelacesToSpend: 90n,
455
+ maxPrice: { getOnChainInt: 1_000_000n },
456
+ owner: '',
457
+ },
458
+ ],
459
+ [
460
+ mockUtxo(80n),
461
+ {
462
+ iasset: 'iUSD',
463
+ lovelacesToSpend: 80n,
464
+ maxPrice: { getOnChainInt: 1_000_000n },
465
+ owner: '',
466
+ },
467
+ ],
468
+ [
469
+ mockUtxo(70n),
470
+ {
471
+ iasset: 'iUSD',
472
+ lovelacesToSpend: 70n,
473
+ maxPrice: { getOnChainInt: 1_000_000n },
474
+ owner: '',
475
+ },
476
+ ],
477
+ [
478
+ mockUtxo(100n),
479
+ {
480
+ iasset: 'iUSD',
481
+ lovelacesToSpend: 100n,
482
+ maxPrice: { getOnChainInt: 1_000_000n },
483
+ owner: '',
484
+ },
485
+ ],
486
+ ];
487
+
488
+ const mockedShuffle = vi.fn().mockImplementation(() => lrps);
489
+
490
+ expect(MAX_REDEMPTIONS_WITH_CDP_OPEN).toBe(4);
491
+
492
+ expect(
493
+ randomLrpsSubsetSatisfyingTargetLovelaces(
494
+ 'iUSD',
495
+ 360n,
496
+ { getOnChainInt: 1_000_000n },
497
+ lrps,
498
+ mockLrpParams,
499
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
500
+ mockedShuffle,
501
+ ),
502
+ ).toEqual(expect.arrayContaining([lrps[0], lrps[1], lrps[2], lrps[4]]));
503
+ });
504
+ });
505
+
506
+ describe('lrpRedeemableLovelacesInclReimb', () => {
507
+ const mockUtxo = (ada: bigint): UTxO => ({
508
+ address: '',
509
+ assets: mkLovelacesOf(ada),
510
+ outputIndex: 0,
511
+ txHash: '',
512
+ });
513
+
514
+ const mockLrpParams: LrpParamsSP = {
515
+ iassetNft: [{ unCurrencySymbol: '' }, { unTokenName: '' }],
516
+ iassetPolicyId: { unCurrencySymbol: '' },
517
+ minRedemptionLovelacesAmt: 10n,
518
+ versionRecordToken: [{ unCurrencySymbol: '' }, { unTokenName: '' }],
519
+ };
520
+
521
+ test('1', () => {
522
+ expect(
523
+ lrpRedeemableLovelacesInclReimb(
524
+ [
525
+ mockUtxo(110n),
526
+ {
527
+ iasset: 'iUSD',
528
+ lovelacesToSpend: 100n,
529
+ maxPrice: { getOnChainInt: 1_000_000n },
530
+ owner: '',
531
+ },
532
+ ],
533
+ mockLrpParams,
534
+ ),
535
+ ).toEqual<bigint>(100n);
536
+ });
537
+
538
+ test('capped to UTXO value', () => {
539
+ expect(
540
+ lrpRedeemableLovelacesInclReimb(
541
+ [
542
+ mockUtxo(20_000_000n),
543
+ {
544
+ iasset: 'iUSD',
545
+ lovelacesToSpend: 100_000_000n,
546
+ maxPrice: { getOnChainInt: 1_000_000n },
547
+ owner: '',
548
+ },
549
+ ],
550
+ mockLrpParams,
551
+ ),
552
+ ).toEqual<bigint>(20_000_000n - MIN_LRP_COLLATERAL_AMT);
553
+ });
554
+
555
+ test('less than min redemption', () => {
556
+ expect(
557
+ lrpRedeemableLovelacesInclReimb(
558
+ [
559
+ mockUtxo(20n),
560
+ {
561
+ iasset: 'iUSD',
562
+ lovelacesToSpend: 5n,
563
+ maxPrice: { getOnChainInt: 1_000_000n },
564
+ owner: '',
565
+ },
566
+ ],
567
+ mockLrpParams,
568
+ ),
569
+ ).toEqual<bigint>(0n);
570
+ });
571
+ });
572
+
573
+ describe('calculateTotalAdaForRedemption', () => {
574
+ const mockUtxo = (ada: bigint): UTxO => ({
575
+ address: '',
576
+ assets: mkLovelacesOf(ada),
577
+ outputIndex: 0,
578
+ txHash: '',
579
+ });
580
+
581
+ const mockLrpParams: LrpParamsSP = {
582
+ iassetNft: [{ unCurrencySymbol: '' }, { unTokenName: '' }],
583
+ iassetPolicyId: { unCurrencySymbol: '' },
584
+ minRedemptionLovelacesAmt: 10n,
585
+ versionRecordToken: [{ unCurrencySymbol: '' }, { unTokenName: '' }],
586
+ };
587
+
588
+ test('1', () => {
589
+ const lrps: [UTxO, LRPDatum][] = [
590
+ [
591
+ mockUtxo(100n),
592
+ {
593
+ iasset: 'iUSD',
594
+ lovelacesToSpend: 100n,
595
+ maxPrice: { getOnChainInt: 1_000_000n },
596
+ owner: '',
597
+ },
598
+ ],
599
+ [
600
+ mockUtxo(100n),
601
+ {
602
+ iasset: 'iUSD',
603
+ lovelacesToSpend: 100n,
604
+ maxPrice: { getOnChainInt: 1_000_000n },
605
+ owner: '',
606
+ },
607
+ ],
608
+ ];
609
+
610
+ expect(
611
+ calculateTotalAdaForRedemption(
612
+ 'iUSD',
613
+ { getOnChainInt: 1_000_000n },
614
+ mockLrpParams,
615
+ lrps,
616
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
617
+ ),
618
+ // Because of rounding, the reimbursement isn't subtracted
619
+ ).toEqual<bigint>(200n);
620
+ });
621
+
622
+ test('2', () => {
623
+ const lrps: [UTxO, LRPDatum][] = [
624
+ [
625
+ mockUtxo(1000n),
626
+ {
627
+ iasset: 'iUSD',
628
+ lovelacesToSpend: 1000n,
629
+ maxPrice: { getOnChainInt: 1_000_000n },
630
+ owner: '',
631
+ },
632
+ ],
633
+ [
634
+ mockUtxo(1000n),
635
+ {
636
+ iasset: 'iUSD',
637
+ lovelacesToSpend: 1000n,
638
+ maxPrice: { getOnChainInt: 1_000_000n },
639
+ owner: '',
640
+ },
641
+ ],
642
+ ];
643
+
644
+ expect(
645
+ calculateTotalAdaForRedemption(
646
+ 'iUSD',
647
+ { getOnChainInt: 1_000_000n },
648
+ mockLrpParams,
649
+ lrps,
650
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
651
+ ),
652
+ ).toEqual<bigint>(2000n);
653
+ });
654
+
655
+ test('filtering by assets 1', () => {
656
+ const lrps: [UTxO, LRPDatum][] = [
657
+ [
658
+ mockUtxo(1000n),
659
+ {
660
+ iasset: 'iUSD',
661
+ lovelacesToSpend: 1000n,
662
+ maxPrice: { getOnChainInt: 1_000_000n },
663
+ owner: '',
664
+ },
665
+ ],
666
+ [
667
+ mockUtxo(1000n),
668
+ {
669
+ iasset: 'iBTC',
670
+ lovelacesToSpend: 1000n,
671
+ maxPrice: { getOnChainInt: 1_000_000n },
672
+ owner: '',
673
+ },
674
+ ],
675
+ [
676
+ mockUtxo(1000n),
677
+ {
678
+ iasset: 'iETH',
679
+ lovelacesToSpend: 1000n,
680
+ maxPrice: { getOnChainInt: 1_000_000n },
681
+ owner: '',
682
+ },
683
+ ],
684
+ ];
685
+
686
+ expect(
687
+ calculateTotalAdaForRedemption(
688
+ 'iUSD',
689
+ { getOnChainInt: 1_000_000n },
690
+ mockLrpParams,
691
+ lrps,
692
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
693
+ ),
694
+ ).toEqual<bigint>(1000n);
695
+ });
696
+
697
+ test('filtering by price 1', () => {
698
+ const lrps: [UTxO, LRPDatum][] = [
699
+ [
700
+ mockUtxo(1000n),
701
+ {
702
+ iasset: 'iUSD',
703
+ lovelacesToSpend: 1000n,
704
+ maxPrice: { getOnChainInt: 1_000_000n },
705
+ owner: '',
706
+ },
707
+ ],
708
+ [
709
+ mockUtxo(1000n),
710
+ {
711
+ iasset: 'iUSD',
712
+ lovelacesToSpend: 1000n,
713
+ maxPrice: { getOnChainInt: 1_500_000n },
714
+ owner: '',
715
+ },
716
+ ],
717
+ [
718
+ mockUtxo(1000n),
719
+ {
720
+ iasset: 'iUSD',
721
+ lovelacesToSpend: 1000n,
722
+ maxPrice: { getOnChainInt: 800_000n },
723
+ owner: '',
724
+ },
725
+ ],
726
+ ];
727
+
728
+ expect(
729
+ calculateTotalAdaForRedemption(
730
+ 'iUSD',
731
+ { getOnChainInt: 1_100_000n },
732
+ mockLrpParams,
733
+ lrps,
734
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
735
+ ),
736
+ ).toEqual<bigint>(1000n);
737
+ });
738
+
739
+ test('capping by max redemptions 1', () => {
740
+ const lrps: [UTxO, LRPDatum][] = [
741
+ [
742
+ mockUtxo(1000n),
743
+ {
744
+ iasset: 'iUSD',
745
+ lovelacesToSpend: 1000n,
746
+ maxPrice: { getOnChainInt: 1_000_000n },
747
+ owner: '',
748
+ },
749
+ ],
750
+ [
751
+ mockUtxo(1400n),
752
+ {
753
+ iasset: 'iUSD',
754
+ lovelacesToSpend: 1400n,
755
+ maxPrice: { getOnChainInt: 1_000_000n },
756
+ owner: '',
757
+ },
758
+ ],
759
+ [
760
+ mockUtxo(1600n),
761
+ {
762
+ iasset: 'iUSD',
763
+ lovelacesToSpend: 1600n,
764
+ maxPrice: { getOnChainInt: 1_000_000n },
765
+ owner: '',
766
+ },
767
+ ],
768
+ [
769
+ mockUtxo(1800n),
770
+ {
771
+ iasset: 'iUSD',
772
+ lovelacesToSpend: 1800n,
773
+ maxPrice: { getOnChainInt: 1_000_000n },
774
+ owner: '',
775
+ },
776
+ ],
777
+ [
778
+ mockUtxo(2000n),
779
+ {
780
+ iasset: 'iUSD',
781
+ lovelacesToSpend: 2000n,
782
+ maxPrice: { getOnChainInt: 1_000_000n },
783
+ owner: '',
784
+ },
785
+ ],
786
+ ];
787
+
788
+ expect(MAX_REDEMPTIONS_WITH_CDP_OPEN).toBe(4);
789
+
790
+ expect(
791
+ calculateTotalAdaForRedemption(
792
+ 'iUSD',
793
+ { getOnChainInt: 1_000_000n },
794
+ mockLrpParams,
795
+ lrps,
796
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
797
+ ),
798
+ // I.e. the one with 1000n lovelaces is dropped
799
+ ).toEqual<bigint>(6800n);
800
+ });
801
+
802
+ test('incorrectly initialised LRPs 1', () => {
803
+ const lrps: [UTxO, LRPDatum][] = [
804
+ // This one should be capped to the UTXO value
805
+ [
806
+ mockUtxo(20_000_000n),
807
+ {
808
+ iasset: 'iUSD',
809
+ lovelacesToSpend: 100_000_000n,
810
+ maxPrice: { getOnChainInt: 1_000_000n },
811
+ owner: '',
812
+ },
813
+ ],
814
+ [
815
+ mockUtxo(1000n),
816
+ {
817
+ iasset: 'iUSD',
818
+ lovelacesToSpend: 1000n,
819
+ maxPrice: { getOnChainInt: 1_000_000n },
820
+ owner: '',
821
+ },
822
+ ],
823
+ [
824
+ mockUtxo(1000n),
825
+ {
826
+ iasset: 'iUSD',
827
+ lovelacesToSpend: 1000n,
828
+ maxPrice: { getOnChainInt: 1_000_000n },
829
+ owner: '',
830
+ },
831
+ ],
832
+ // This one shold get dropped since less than min
833
+ [
834
+ mockUtxo(1000n),
835
+ {
836
+ iasset: 'iUSD',
837
+ lovelacesToSpend: 5n,
838
+ maxPrice: { getOnChainInt: 1_000_000n },
839
+ owner: '',
840
+ },
841
+ ],
842
+ ];
843
+
844
+ expect(
845
+ calculateTotalAdaForRedemption(
846
+ 'iUSD',
847
+ { getOnChainInt: 1_000_000n },
848
+ mockLrpParams,
849
+ lrps,
850
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
851
+ ),
852
+ ).toEqual<bigint>(18_002_000n);
853
+ });
854
+ });
855
+
856
+ describe('LRP leverage', () => {
857
+ beforeEach<MyContext>(async (context: MyContext) => {
858
+ context.users = {
859
+ admin: generateEmulatorAccount({
860
+ lovelace: BigInt(100_000_000_000_000),
861
+ }),
862
+ user: generateEmulatorAccount(addAssets(mkLovelacesOf(150_000_000n))),
863
+ };
864
+
865
+ context.emulator = new Emulator([context.users.admin, context.users.user]);
866
+ context.lucid = await Lucid(context.emulator, 'Custom');
867
+ });
868
+
869
+ test<MyContext>('Open 2x leveraged CDP; 1 LRP; price ~1.1; f_r=.01; f_m=.005', async (context: MyContext) => {
870
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
871
+
872
+ const [sysParams, __] = await init(context.lucid, [
873
+ {
874
+ ...iusdInitialAssetCfg,
875
+ priceOracle: {
876
+ ...iusdInitialAssetCfg.priceOracle,
877
+ startPrice: 1_104_093n,
878
+ },
879
+ interestOracle: {
880
+ ...iusdInitialAssetCfg.interestOracle,
881
+ initialInterestRate: 0n,
882
+ },
883
+ maintenanceRatioPercentage: 150_000_000n,
884
+ debtMintingFeePercentage: 500_000n,
885
+ redemptionReimbursementPercentage: 1_000_000n,
886
+ },
887
+ ]);
888
+
889
+ const iasset = fromText(iusdInitialAssetCfg.name);
890
+
891
+ await openLrps(context, sysParams, iasset, [100_000_000n], {
892
+ getOnChainInt: 1_500_000n,
893
+ });
894
+
895
+ const allLrps = await findAllLrps(context.lucid, sysParams, iasset);
896
+
897
+ const orefs = await findAllNecessaryOrefs(
898
+ context.lucid,
899
+ sysParams,
900
+ toText(iasset),
901
+ );
902
+
903
+ const baseCollateral = 20_000_000n;
904
+ await runAndAwaitTx(
905
+ context.lucid,
906
+ leverageCdpWithLrp(
907
+ 2,
908
+ baseCollateral,
909
+ orefs.priceOracleUtxo,
910
+ orefs.iasset.utxo,
911
+ orefs.cdpCreatorUtxo,
912
+ orefs.interestOracleUtxo,
913
+ orefs.collectorUtxo,
914
+ sysParams,
915
+ context.lucid,
916
+ allLrps.map((lrps) => [lrps.utxo, lrps.datum]),
917
+ context.emulator.slot,
918
+ ),
919
+ );
920
+
921
+ const [pkh, skh] = await addrDetails(context.lucid);
922
+
923
+ const res = await findCdp(
924
+ context.lucid,
925
+ sysParams.validatorHashes.cdpHash,
926
+ fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
927
+ pkh.hash,
928
+ skh,
929
+ );
930
+
931
+ // Assert leverage
932
+ assertValueInRange(
933
+ Number(lovelacesAmt(res.utxo.assets)) / Number(baseCollateral),
934
+ {
935
+ min: 2,
936
+ max: 2.001,
937
+ },
938
+ );
939
+
940
+ // Assert collateral ratio
941
+ assertValueInRange(
942
+ cdpCollateralRatioPercentage(
943
+ context.emulator.slot,
944
+ parsePriceOracleDatum(getInlineDatumOrThrow(orefs.priceOracleUtxo))
945
+ .price,
946
+ res.utxo,
947
+ res.datum,
948
+ parseInterestOracleDatum(
949
+ getInlineDatumOrThrow(orefs.interestOracleUtxo),
950
+ ),
951
+ context.lucid.config().network!,
952
+ ),
953
+ {
954
+ min: 197,
955
+ max: 197.001,
956
+ },
957
+ );
958
+ });
959
+
960
+ test<MyContext>('Open 2x leveraged CDP; 4 LRPs; price ~0.9; f_r=.01; f_m=.005', async (context: MyContext) => {
961
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
962
+
963
+ const [sysParams, __] = await init(context.lucid, [
964
+ {
965
+ ...iusdInitialAssetCfg,
966
+ priceOracle: {
967
+ ...iusdInitialAssetCfg.priceOracle,
968
+ startPrice: 904_093n,
969
+ },
970
+ interestOracle: {
971
+ ...iusdInitialAssetCfg.interestOracle,
972
+ initialInterestRate: 0n,
973
+ },
974
+ maintenanceRatioPercentage: 150_000_000n,
975
+ debtMintingFeePercentage: 500_000n,
976
+ redemptionReimbursementPercentage: 1_000_000n,
977
+ },
978
+ ]);
979
+
980
+ const iasset = fromText(iusdInitialAssetCfg.name);
981
+
982
+ await openLrps(
983
+ context,
984
+ sysParams,
985
+ iasset,
986
+ [26_250_000n, 26_250_000n, 26_250_000n, 26_250_000n],
987
+ {
988
+ getOnChainInt: 1_500_000n,
989
+ },
990
+ );
991
+
992
+ const allLrps = await findAllLrps(context.lucid, sysParams, iasset);
993
+
994
+ const orefs = await findAllNecessaryOrefs(
995
+ context.lucid,
996
+ sysParams,
997
+ toText(iasset),
998
+ );
999
+
1000
+ const baseCollateral = 100_000_000n;
1001
+ await runAndAwaitTx(
1002
+ context.lucid,
1003
+ leverageCdpWithLrp(
1004
+ 2,
1005
+ baseCollateral,
1006
+ orefs.priceOracleUtxo,
1007
+ orefs.iasset.utxo,
1008
+ orefs.cdpCreatorUtxo,
1009
+ orefs.interestOracleUtxo,
1010
+ orefs.collectorUtxo,
1011
+ sysParams,
1012
+ context.lucid,
1013
+ allLrps.map((lrps) => [lrps.utxo, lrps.datum]),
1014
+ context.emulator.slot,
1015
+ ),
1016
+ );
1017
+
1018
+ const [pkh, skh] = await addrDetails(context.lucid);
1019
+
1020
+ const res = await findCdp(
1021
+ context.lucid,
1022
+ sysParams.validatorHashes.cdpHash,
1023
+ fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
1024
+ pkh.hash,
1025
+ skh,
1026
+ );
1027
+
1028
+ // Assert leverage
1029
+ assertValueInRange(
1030
+ Number(lovelacesAmt(res.utxo.assets)) / Number(baseCollateral),
1031
+ {
1032
+ min: 2,
1033
+ max: 2.001,
1034
+ },
1035
+ );
1036
+
1037
+ // Assert collateral ratio
1038
+ assertValueInRange(
1039
+ cdpCollateralRatioPercentage(
1040
+ context.emulator.slot,
1041
+ parsePriceOracleDatum(getInlineDatumOrThrow(orefs.priceOracleUtxo))
1042
+ .price,
1043
+ res.utxo,
1044
+ res.datum,
1045
+ parseInterestOracleDatum(
1046
+ getInlineDatumOrThrow(orefs.interestOracleUtxo),
1047
+ ),
1048
+ context.lucid.config().network!,
1049
+ ),
1050
+ {
1051
+ min: 197,
1052
+ max: 197.001,
1053
+ },
1054
+ );
1055
+
1056
+ {
1057
+ const lrps = await findAllLrps(context.lucid, sysParams, iasset);
1058
+ expect(
1059
+ lrps.every((lrp) => hadLrpRedemption(lrp, sysParams.lrpParams)),
1060
+ ).toBeTruthy();
1061
+ }
1062
+ });
1063
+
1064
+ test<MyContext>('Open 2.3x leveraged CDP; 4 LRPs; price ~1.03; f_r=.01; f_m=.013', async (context: MyContext) => {
1065
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1066
+
1067
+ const [sysParams, __] = await init(context.lucid, [
1068
+ {
1069
+ ...iusdInitialAssetCfg,
1070
+ priceOracle: {
1071
+ ...iusdInitialAssetCfg.priceOracle,
1072
+ startPrice: 1_037_093n,
1073
+ },
1074
+ interestOracle: {
1075
+ ...iusdInitialAssetCfg.interestOracle,
1076
+ initialInterestRate: 0n,
1077
+ },
1078
+ maintenanceRatioPercentage: 150_000_000n,
1079
+ debtMintingFeePercentage: 1_300_000n,
1080
+ redemptionReimbursementPercentage: 1_000_000n,
1081
+ },
1082
+ ]);
1083
+
1084
+ const iasset = fromText(iusdInitialAssetCfg.name);
1085
+
1086
+ await openLrps(
1087
+ context,
1088
+ sysParams,
1089
+ iasset,
1090
+ [35_139_729n, 35_000_397n, 35_001_079n, 35_107_049n],
1091
+ {
1092
+ getOnChainInt: 1_500_000n,
1093
+ },
1094
+ );
1095
+
1096
+ const allLrps = await findAllLrps(context.lucid, sysParams, iasset);
1097
+
1098
+ const orefs = await findAllNecessaryOrefs(
1099
+ context.lucid,
1100
+ sysParams,
1101
+ toText(iasset),
1102
+ );
1103
+
1104
+ const baseCollateral = 100_000_000n;
1105
+ await runAndAwaitTx(
1106
+ context.lucid,
1107
+ leverageCdpWithLrp(
1108
+ 2.3,
1109
+ baseCollateral,
1110
+ orefs.priceOracleUtxo,
1111
+ orefs.iasset.utxo,
1112
+ orefs.cdpCreatorUtxo,
1113
+ orefs.interestOracleUtxo,
1114
+ orefs.collectorUtxo,
1115
+ sysParams,
1116
+ context.lucid,
1117
+ allLrps.map((lrps) => [lrps.utxo, lrps.datum]),
1118
+ context.emulator.slot,
1119
+ ),
1120
+ );
1121
+
1122
+ const [pkh, skh] = await addrDetails(context.lucid);
1123
+
1124
+ const res = await findCdp(
1125
+ context.lucid,
1126
+ sysParams.validatorHashes.cdpHash,
1127
+ fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
1128
+ pkh.hash,
1129
+ skh,
1130
+ );
1131
+
1132
+ // Assert leverage
1133
+ assertValueInRange(
1134
+ Number(lovelacesAmt(res.utxo.assets)) / Number(baseCollateral),
1135
+ {
1136
+ min: 2.3,
1137
+ max: 2.301,
1138
+ },
1139
+ );
1140
+
1141
+ // Assert collateral ratio
1142
+ assertValueInRange(
1143
+ cdpCollateralRatioPercentage(
1144
+ context.emulator.slot,
1145
+ parsePriceOracleDatum(getInlineDatumOrThrow(orefs.priceOracleUtxo))
1146
+ .price,
1147
+ res.utxo,
1148
+ res.datum,
1149
+ parseInterestOracleDatum(
1150
+ getInlineDatumOrThrow(orefs.interestOracleUtxo),
1151
+ ),
1152
+ context.lucid.config().network!,
1153
+ ),
1154
+ {
1155
+ min: 172.8,
1156
+ max: 172.9,
1157
+ },
1158
+ );
1159
+
1160
+ {
1161
+ const lrps = await findAllLrps(context.lucid, sysParams, iasset);
1162
+ expect(
1163
+ lrps.every((lrp) => hadLrpRedemption(lrp, sysParams.lrpParams)),
1164
+ ).toBeTruthy();
1165
+ }
1166
+ });
1167
+
1168
+ test<MyContext>('Open 1.2x leveraged CDP 3 LRPs price ~1.46; f_r=.02; f_m=.007', async (context: MyContext) => {
1169
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1170
+
1171
+ const [sysParams, __] = await init(context.lucid, [
1172
+ {
1173
+ ...iusdInitialAssetCfg,
1174
+ priceOracle: {
1175
+ ...iusdInitialAssetCfg.priceOracle,
1176
+ startPrice: 1_461_093n,
1177
+ },
1178
+ interestOracle: {
1179
+ ...iusdInitialAssetCfg.interestOracle,
1180
+ initialInterestRate: 0n,
1181
+ },
1182
+ maintenanceRatioPercentage: 150_000_000n,
1183
+ debtMintingFeePercentage: 700_000n,
1184
+ redemptionReimbursementPercentage: 2_000_000n,
1185
+ },
1186
+ ]);
1187
+
1188
+ const iasset = fromText(iusdInitialAssetCfg.name);
1189
+
1190
+ await openLrps(
1191
+ context,
1192
+ sysParams,
1193
+ iasset,
1194
+ [75_000_000n, 75_000_000n, 75_000_000n],
1195
+ {
1196
+ getOnChainInt: 1_500_000n,
1197
+ },
1198
+ );
1199
+
1200
+ const allLrps = await findAllLrps(context.lucid, sysParams, iasset);
1201
+
1202
+ const orefs = await findAllNecessaryOrefs(
1203
+ context.lucid,
1204
+ sysParams,
1205
+ toText(iasset),
1206
+ );
1207
+
1208
+ const baseCollateral = 1_000_000_000n;
1209
+ await runAndAwaitTx(
1210
+ context.lucid,
1211
+ leverageCdpWithLrp(
1212
+ 1.2,
1213
+ baseCollateral,
1214
+ orefs.priceOracleUtxo,
1215
+ orefs.iasset.utxo,
1216
+ orefs.cdpCreatorUtxo,
1217
+ orefs.interestOracleUtxo,
1218
+ orefs.collectorUtxo,
1219
+ sysParams,
1220
+ context.lucid,
1221
+ allLrps.map((lrps) => [lrps.utxo, lrps.datum]),
1222
+ context.emulator.slot,
1223
+ ),
1224
+ );
1225
+
1226
+ const [pkh, skh] = await addrDetails(context.lucid);
1227
+
1228
+ const res = await findCdp(
1229
+ context.lucid,
1230
+ sysParams.validatorHashes.cdpHash,
1231
+ fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
1232
+ pkh.hash,
1233
+ skh,
1234
+ );
1235
+
1236
+ // Assert leverage
1237
+ assertValueInRange(
1238
+ Number(lovelacesAmt(res.utxo.assets)) / Number(baseCollateral),
1239
+ {
1240
+ min: 1.2,
1241
+ max: 1.2001,
1242
+ },
1243
+ );
1244
+
1245
+ // Assert collateral ratio
1246
+ assertValueInRange(
1247
+ cdpCollateralRatioPercentage(
1248
+ context.emulator.slot,
1249
+ parsePriceOracleDatum(getInlineDatumOrThrow(orefs.priceOracleUtxo))
1250
+ .price,
1251
+ res.utxo,
1252
+ res.datum,
1253
+ parseInterestOracleDatum(
1254
+ getInlineDatumOrThrow(orefs.interestOracleUtxo),
1255
+ ),
1256
+ context.lucid.config().network!,
1257
+ ),
1258
+ {
1259
+ min: 583.79,
1260
+ max: 583.8,
1261
+ },
1262
+ );
1263
+
1264
+ {
1265
+ const lrps = await findAllLrps(context.lucid, sysParams, iasset);
1266
+ expect(
1267
+ lrps.every((lrp) => hadLrpRedemption(lrp, sysParams.lrpParams)),
1268
+ ).toBeTruthy();
1269
+ }
1270
+ });
1271
+
1272
+ test<MyContext>('Open max leverage leveraged CDP; 4 CDPs; price 1; f_r=.01; f_m=.005', async (context: MyContext) => {
1273
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1274
+
1275
+ const [sysParams, __] = await init(context.lucid, [
1276
+ {
1277
+ ...iusdInitialAssetCfg,
1278
+ priceOracle: {
1279
+ ...iusdInitialAssetCfg.priceOracle,
1280
+ startPrice: 1_000_000n,
1281
+ },
1282
+ interestOracle: {
1283
+ ...iusdInitialAssetCfg.interestOracle,
1284
+ initialInterestRate: 0n,
1285
+ },
1286
+ maintenanceRatioPercentage: 150_000_000n,
1287
+ debtMintingFeePercentage: 500_000n,
1288
+ redemptionReimbursementPercentage: 1_000_000n,
1289
+ },
1290
+ ]);
1291
+
1292
+ const iasset = fromText(iusdInitialAssetCfg.name);
1293
+
1294
+ await openLrps(
1295
+ context,
1296
+ sysParams,
1297
+ iasset,
1298
+ [500_000_000n, 500_000_000n, 500_000_000n, 500_000_000n],
1299
+ {
1300
+ getOnChainInt: 1_500_000n,
1301
+ },
1302
+ );
1303
+
1304
+ const allLrps = await findAllLrps(context.lucid, sysParams, iasset);
1305
+
1306
+ const orefs = await findAllNecessaryOrefs(
1307
+ context.lucid,
1308
+ sysParams,
1309
+ toText(iasset),
1310
+ );
1311
+
1312
+ const baseCollateral = 1_000_000_000n;
1313
+ const maxLeverage = calculateLeverageFromCollateralRatio(
1314
+ iasset,
1315
+ orefs.iasset.datum.maintenanceRatio,
1316
+ baseCollateral,
1317
+ parsePriceOracleDatum(getInlineDatumOrThrow(orefs.priceOracleUtxo)).price,
1318
+ orefs.iasset.datum.debtMintingFeePercentage,
1319
+ orefs.iasset.datum.redemptionReimbursementPercentage,
1320
+ sysParams.lrpParams,
1321
+ allLrps.map((lrps) => [lrps.utxo, lrps.datum]),
1322
+ )!;
1323
+
1324
+ await runAndAwaitTx(
1325
+ context.lucid,
1326
+ leverageCdpWithLrp(
1327
+ maxLeverage,
1328
+ baseCollateral,
1329
+ orefs.priceOracleUtxo,
1330
+ orefs.iasset.utxo,
1331
+ orefs.cdpCreatorUtxo,
1332
+ orefs.interestOracleUtxo,
1333
+ orefs.collectorUtxo,
1334
+ sysParams,
1335
+ context.lucid,
1336
+ allLrps.map((lrps) => [lrps.utxo, lrps.datum]),
1337
+ context.emulator.slot,
1338
+ ),
1339
+ );
1340
+
1341
+ const [pkh, skh] = await addrDetails(context.lucid);
1342
+
1343
+ const res = await findCdp(
1344
+ context.lucid,
1345
+ sysParams.validatorHashes.cdpHash,
1346
+ fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
1347
+ pkh.hash,
1348
+ skh,
1349
+ );
1350
+
1351
+ // Assert leverage
1352
+ assertValueInRange(
1353
+ Number(lovelacesAmt(res.utxo.assets)) / Number(baseCollateral),
1354
+ {
1355
+ min: 2.9126,
1356
+ max: 2.9127,
1357
+ },
1358
+ );
1359
+
1360
+ // Assert collateral ratio
1361
+ assertValueInRange(
1362
+ cdpCollateralRatioPercentage(
1363
+ context.emulator.slot,
1364
+ parsePriceOracleDatum(getInlineDatumOrThrow(orefs.priceOracleUtxo))
1365
+ .price,
1366
+ res.utxo,
1367
+ res.datum,
1368
+ parseInterestOracleDatum(
1369
+ getInlineDatumOrThrow(orefs.interestOracleUtxo),
1370
+ ),
1371
+ context.lucid.config().network!,
1372
+ ),
1373
+ {
1374
+ min: Number(ocdFloor(orefs.iasset.datum.maintenanceRatio)),
1375
+ max: Number(ocdFloor(orefs.iasset.datum.maintenanceRatio)) + 1,
1376
+ },
1377
+ );
1378
+
1379
+ {
1380
+ const lrps = await findAllLrps(context.lucid, sysParams, iasset);
1381
+ expect(
1382
+ lrps.every((lrp) => hadLrpRedemption(lrp, sysParams.lrpParams)),
1383
+ ).toBeTruthy();
1384
+ }
1385
+ });
1386
+
1387
+ test<MyContext>('Open max leverage leveraged CDP; 2 CDPs; price 2.5; f_r=.014; f_m=.006', async (context: MyContext) => {
1388
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1389
+
1390
+ const [sysParams, __] = await init(context.lucid, [
1391
+ {
1392
+ ...iusdInitialAssetCfg,
1393
+ priceOracle: {
1394
+ ...iusdInitialAssetCfg.priceOracle,
1395
+ startPrice: 2_500_000n,
1396
+ },
1397
+ interestOracle: {
1398
+ ...iusdInitialAssetCfg.interestOracle,
1399
+ initialInterestRate: 0n,
1400
+ },
1401
+ maintenanceRatioPercentage: 130_000_000n,
1402
+ debtMintingFeePercentage: 600_000n,
1403
+ redemptionReimbursementPercentage: 1_400_000n,
1404
+ },
1405
+ ]);
1406
+
1407
+ const iasset = fromText(iusdInitialAssetCfg.name);
1408
+
1409
+ await openLrps(context, sysParams, iasset, [325_000_000n, 325_000_000n], {
1410
+ getOnChainInt: 3_000_000n,
1411
+ });
1412
+
1413
+ const allLrps = await findAllLrps(context.lucid, sysParams, iasset);
1414
+
1415
+ const orefs = await findAllNecessaryOrefs(
1416
+ context.lucid,
1417
+ sysParams,
1418
+ toText(iasset),
1419
+ );
1420
+
1421
+ const baseCollateral = 200_000_000n;
1422
+ const maxLeverage = calculateLeverageFromCollateralRatio(
1423
+ iasset,
1424
+ orefs.iasset.datum.maintenanceRatio,
1425
+ baseCollateral,
1426
+ parsePriceOracleDatum(getInlineDatumOrThrow(orefs.priceOracleUtxo)).price,
1427
+ orefs.iasset.datum.debtMintingFeePercentage,
1428
+ orefs.iasset.datum.redemptionReimbursementPercentage,
1429
+ sysParams.lrpParams,
1430
+ allLrps.map((lrps) => [lrps.utxo, lrps.datum]),
1431
+ )!;
1432
+
1433
+ await runAndAwaitTx(
1434
+ context.lucid,
1435
+ leverageCdpWithLrp(
1436
+ maxLeverage,
1437
+ baseCollateral,
1438
+ orefs.priceOracleUtxo,
1439
+ orefs.iasset.utxo,
1440
+ orefs.cdpCreatorUtxo,
1441
+ orefs.interestOracleUtxo,
1442
+ orefs.collectorUtxo,
1443
+ sysParams,
1444
+ context.lucid,
1445
+ allLrps.map((lrps) => [lrps.utxo, lrps.datum]),
1446
+ context.emulator.slot,
1447
+ ),
1448
+ );
1449
+
1450
+ const [pkh, skh] = await addrDetails(context.lucid);
1451
+
1452
+ const res = await findCdp(
1453
+ context.lucid,
1454
+ sysParams.validatorHashes.cdpHash,
1455
+ fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
1456
+ pkh.hash,
1457
+ skh,
1458
+ );
1459
+
1460
+ // Assert leverage
1461
+ assertValueInRange(
1462
+ Number(lovelacesAmt(res.utxo.assets)) / Number(baseCollateral),
1463
+ {
1464
+ min: 4.0625,
1465
+ max: 4.06251,
1466
+ },
1467
+ );
1468
+
1469
+ // Assert collateral ratio
1470
+ assertValueInRange(
1471
+ cdpCollateralRatioPercentage(
1472
+ context.emulator.slot,
1473
+ parsePriceOracleDatum(getInlineDatumOrThrow(orefs.priceOracleUtxo))
1474
+ .price,
1475
+ res.utxo,
1476
+ res.datum,
1477
+ parseInterestOracleDatum(
1478
+ getInlineDatumOrThrow(orefs.interestOracleUtxo),
1479
+ ),
1480
+ context.lucid.config().network!,
1481
+ ),
1482
+ {
1483
+ min: Number(ocdFloor(orefs.iasset.datum.maintenanceRatio)),
1484
+ max: Number(ocdFloor(orefs.iasset.datum.maintenanceRatio)) + 1,
1485
+ },
1486
+ );
1487
+
1488
+ {
1489
+ const lrps = await findAllLrps(context.lucid, sysParams, iasset);
1490
+ expect(
1491
+ lrps.every((lrp) => hadLrpRedemption(lrp, sysParams.lrpParams)),
1492
+ ).toBeTruthy();
1493
+ }
1494
+ });
1495
+ });