@indigo-labs/indigo-sdk 0.3.9 → 0.3.10

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.
@@ -51,374 +51,382 @@
51
51
  * `b’’ = d(L - 1)`
52
52
  */
53
53
 
54
- // import { UTxO } from '@lucid-evolution/lucid';
55
- // import {
56
- // OCD_DECIMAL_UNIT,
57
- // ocdMul,
58
- // OnChainDecimal,
59
- // } from '../../types/on-chain-decimal';
60
- // import { calculateFeeFromPercentage } from '../../utils/indigo-helpers';
61
- // import { bigintMax, bigintMin, fromDecimal } from '../../utils/bigint-utils';
62
- // import { array as A, function as F } from 'fp-ts';
63
- // import { Decimal } from 'decimal.js';
64
- // import { RobParamsSP } from '../../types/system-params';
65
- // import {
66
- // calculateTotalAdaForRedemption,
67
- // lrpRedeemableLovelacesInclReimb,
68
- // } from '../lrp/helpers';
69
- // import { RobDatum } from '../lrp/types-new';
70
-
71
- // /**
72
- // * How many LRP redemptions can we fit into a TX with CDP open.
73
- // */
74
- // export const MAX_REDEMPTIONS_WITH_CDP_OPEN = 4;
75
-
76
- // type LRPRedemptionDetails = {
77
- // utxo: UTxO;
78
- // /**
79
- // * This is including the reimbursement fee.
80
- // **/
81
- // redemptionLovelacesAmtInclReimbursement: bigint;
82
- // iassetsForRedemptionAmt: bigint;
83
- // reimbursementLovelacesAmt: bigint;
84
- // };
85
-
86
- // type ApproximateLeverageRedemptionsResult = {
87
- // leverage: number;
88
- // collateralRatio: OnChainDecimal;
89
- // lovelacesForRedemptionWithReimbursement: bigint;
90
- // };
91
- // /**
92
- // * We assume exact precision. However, actual redemptions include rounding and
93
- // * the rounding behaviour changes based on the number of redemptions.
94
- // * This may slightly tweak the numbers and the result can be different.
95
- // *
96
- // * The math is described at the top of this code file.
97
- // */
98
- // export function approximateLeverageRedemptions(
99
- // baseCollateral: bigint,
100
- // targetLeverage: number,
101
- // redemptionReimbursementPercentage: OnChainDecimal,
102
- // debtMintingFeePercentage: OnChainDecimal,
103
- // ): ApproximateLeverageRedemptionsResult {
104
- // const debtMintingFeeRatioDecimal = Decimal(
105
- // debtMintingFeePercentage.getOnChainInt,
106
- // )
107
- // .div(OCD_DECIMAL_UNIT)
108
- // .div(100);
109
- // const redemptionReimbursementRatioDecimal = Decimal(
110
- // redemptionReimbursementPercentage.getOnChainInt,
111
- // )
112
- // .div(OCD_DECIMAL_UNIT)
113
- // .div(100);
114
-
115
- // const totalFeeRatio = debtMintingFeeRatioDecimal.add(
116
- // redemptionReimbursementRatioDecimal,
117
- // );
118
-
119
- // // b''
120
- // const bExFees = Decimal(baseCollateral)
121
- // .mul(targetLeverage)
122
- // .minus(baseCollateral)
123
- // .floor();
124
-
125
- // // b = b’’ / (1-f_r - f_m)
126
- // const b = bExFees.div(Decimal(1).minus(totalFeeRatio)).floor();
127
-
128
- // // c = (d + b * (1 - f_r - f_m)) / b
129
- // const collateralRatio = {
130
- // getOnChainInt: fromDecimal(
131
- // Decimal(Decimal(baseCollateral).add(bExFees))
132
- // .div(b)
133
- // .mul(100n * OCD_DECIMAL_UNIT)
134
- // .floor(),
135
- // ),
136
- // };
137
-
138
- // return {
139
- // leverage: targetLeverage,
140
- // collateralRatio: collateralRatio,
141
- // lovelacesForRedemptionWithReimbursement: fromDecimal(b),
142
- // };
143
- // }
144
-
145
- // export function summarizeActualLeverageRedemptions(
146
- // lovelacesForRedemptionWithReimbursement: bigint,
147
- // redemptionReimbursementPercentage: OnChainDecimal,
148
- // iassetPrice: OnChainDecimal,
149
- // lrpParams: RobParamsSP,
150
- // // Picking from the beginning until the iasset redemption amount is satisfied.
151
- // redemptionLrps: [UTxO, RobDatum][],
152
- // ): {
153
- // redemptions: LRPRedemptionDetails[];
154
- // /**
155
- // * The actual amount received from redemptions (i.e. without the reimbursement fee).
156
- // */
157
- // totalRedeemedLovelaces: bigint;
158
- // /**
159
- // * Total lovelaces amt that has been reimbursted
160
- // */
161
- // totalReimbursementLovelaces: bigint;
162
- // totalRedemptionIAssets: bigint;
163
- // } {
164
- // const priceDecimal = Decimal(iassetPrice.getOnChainInt).div(OCD_DECIMAL_UNIT);
165
-
166
- // type Accumulator = {
167
- // /// This is including the redemption reimbursement
168
- // remainingRedemptionLovelacesInclReim: bigint;
169
- // redemptions: LRPRedemptionDetails[];
170
- // };
171
-
172
- // const redemptionDetails = F.pipe(
173
- // redemptionLrps,
174
- // A.reduce<[UTxO, RobDatum], Accumulator>(
175
- // {
176
- // remainingRedemptionLovelacesInclReim:
177
- // lovelacesForRedemptionWithReimbursement,
178
- // redemptions: [],
179
- // },
180
- // (acc, lrp) => {
181
- // if (
182
- // acc.remainingRedemptionLovelacesInclReim <
183
- // lrpParams.minRedemptionLovelacesAmt
184
- // ) {
185
- // return acc;
186
- // }
187
-
188
- // const lovelacesToSpend = lrpRedeemableLovelacesInclReimb(
189
- // lrp,
190
- // lrpParams,
191
- // );
192
-
193
- // if (lovelacesToSpend === 0n) {
194
- // return acc;
195
- // }
196
-
197
- // const newRemainingLovelaces = bigintMax(
198
- // acc.remainingRedemptionLovelacesInclReim - lovelacesToSpend,
199
- // 0n,
200
- // );
201
- // const redemptionLovelacesInitial =
202
- // acc.remainingRedemptionLovelacesInclReim - newRemainingLovelaces;
203
-
204
- // const finalRedemptionIAssets = fromDecimal(
205
- // Decimal(redemptionLovelacesInitial).div(priceDecimal).floor(),
206
- // );
207
- // // We need to calculate the new number since redemptionIAssets got corrected by rounding.
208
- // const finalRedemptionLovelaces = ocdMul(
209
- // {
210
- // getOnChainInt: finalRedemptionIAssets,
211
- // },
212
- // iassetPrice,
213
- // ).getOnChainInt;
214
-
215
- // const reimbursementLovelaces = calculateFeeFromPercentage(
216
- // redemptionReimbursementPercentage,
217
- // finalRedemptionLovelaces,
218
- // );
219
-
220
- // return {
221
- // remainingRedemptionLovelacesInclReim:
222
- // acc.remainingRedemptionLovelacesInclReim - finalRedemptionLovelaces,
223
- // redemptions: [
224
- // ...acc.redemptions,
225
- // {
226
- // utxo: lrp[0],
227
- // iassetsForRedemptionAmt: finalRedemptionIAssets,
228
- // redemptionLovelacesAmtInclReimbursement: finalRedemptionLovelaces,
229
- // reimbursementLovelacesAmt: reimbursementLovelaces,
230
- // },
231
- // ],
232
- // };
233
- // },
234
- // ),
235
- // );
236
-
237
- // const res = F.pipe(
238
- // redemptionDetails.redemptions,
239
- // A.reduce<
240
- // LRPRedemptionDetails,
241
- // {
242
- // redeemedLovelaces: bigint;
243
- // redemptionIAssets: bigint;
244
- // reimbursementLovelaces: bigint;
245
- // }
246
- // >(
247
- // {
248
- // redeemedLovelaces: 0n,
249
- // redemptionIAssets: 0n,
250
- // reimbursementLovelaces: 0n,
251
- // },
252
- // (acc, details) => {
253
- // return {
254
- // redeemedLovelaces:
255
- // acc.redeemedLovelaces +
256
- // details.redemptionLovelacesAmtInclReimbursement -
257
- // details.reimbursementLovelacesAmt,
258
- // reimbursementLovelaces:
259
- // acc.reimbursementLovelaces + details.reimbursementLovelacesAmt,
260
- // redemptionIAssets:
261
- // acc.redemptionIAssets + details.iassetsForRedemptionAmt,
262
- // };
263
- // },
264
- // ),
265
- // );
266
-
267
- // return {
268
- // redemptions: redemptionDetails.redemptions,
269
- // totalRedeemedLovelaces: res.redeemedLovelaces,
270
- // totalReimbursementLovelaces: res.reimbursementLovelaces,
271
- // totalRedemptionIAssets: res.redemptionIAssets,
272
- // };
273
- // }
274
-
275
- // /**
276
- // * The math is described at the top of this code file.
277
- // */
278
- // export function calculateCollateralRatioFromLeverage(
279
- // iasset: Uint8Array<ArrayBufferLike>,
280
- // leverage: number,
281
- // baseCollateral: bigint,
282
- // iassetPrice: OnChainDecimal,
283
- // debtMintingFeePercentage: OnChainDecimal,
284
- // redemptionReimbursementPercentage: OnChainDecimal,
285
- // lrpParams: RobParamsSP,
286
- // allLrps: [UTxO, RobDatum][],
287
- // ): OnChainDecimal | undefined {
288
- // const debtMintingFeeRatioDecimal = Decimal(
289
- // debtMintingFeePercentage.getOnChainInt,
290
- // )
291
- // .div(OCD_DECIMAL_UNIT)
292
- // .div(100);
293
- // const redemptionReimbursementRatioDecimal = Decimal(
294
- // redemptionReimbursementPercentage.getOnChainInt,
295
- // )
296
- // .div(OCD_DECIMAL_UNIT)
297
- // .div(100);
298
-
299
- // const totalFeeRatio = debtMintingFeeRatioDecimal.add(
300
- // redemptionReimbursementRatioDecimal,
301
- // );
302
-
303
- // const maxAvailableAdaForRedemptionInclReimb = calculateTotalAdaForRedemption(
304
- // iasset,
305
- // iassetPrice,
306
- // lrpParams,
307
- // allLrps,
308
- // MAX_REDEMPTIONS_WITH_CDP_OPEN,
309
- // );
310
-
311
- // if (
312
- // leverage <= 1 ||
313
- // baseCollateral <= 0n ||
314
- // maxAvailableAdaForRedemptionInclReimb <= 0n
315
- // ) {
316
- // return undefined;
317
- // }
318
-
319
- // // b''
320
- // const bExFees = Decimal(baseCollateral)
321
- // .mul(leverage)
322
- // .minus(baseCollateral)
323
- // .floor();
324
-
325
- // // b = b’’ / (1-f_r - f_m)
326
- // const b = bExFees.div(Decimal(1).minus(totalFeeRatio)).floor();
327
-
328
- // const cappedB = bigintMin(
329
- // maxAvailableAdaForRedemptionInclReimb,
330
- // fromDecimal(b),
331
- // );
332
-
333
- // const cappedBExFees = Decimal(cappedB)
334
- // .mul(Decimal(1).minus(totalFeeRatio))
335
- // .floor();
336
-
337
- // // c = (d + b * (1 - f_r - f_m)) / b
338
- // const collateralRatio = Decimal(
339
- // Decimal(baseCollateral).add(cappedBExFees),
340
- // ).div(cappedB);
341
-
342
- // return {
343
- // getOnChainInt: fromDecimal(
344
- // collateralRatio.mul(100n * OCD_DECIMAL_UNIT).floor(),
345
- // ),
346
- // };
347
- // }
348
-
349
- // /**
350
- // * The math is described at the top of this code file.
351
- // */
352
- // export function calculateLeverageFromCollateralRatio(
353
- // iasset: Uint8Array<ArrayBufferLike>,
354
- // collateralRatioPercentage: OnChainDecimal,
355
- // baseCollateral: bigint,
356
- // iassetPrice: OnChainDecimal,
357
- // debtMintingFeePercentage: OnChainDecimal,
358
- // redemptionReimbursementPercentage: OnChainDecimal,
359
- // lrpParams: RobParamsSP,
360
- // allLrps: [UTxO, RobDatum][],
361
- // ): number | undefined {
362
- // const debtMintingFeeRatioDecimal = Decimal(
363
- // debtMintingFeePercentage.getOnChainInt,
364
- // )
365
- // .div(OCD_DECIMAL_UNIT)
366
- // .div(100);
367
- // const redemptionReimbursementRatioDecimal = Decimal(
368
- // redemptionReimbursementPercentage.getOnChainInt,
369
- // )
370
- // .div(OCD_DECIMAL_UNIT)
371
- // .div(100);
372
-
373
- // const totalFeeRatio = debtMintingFeeRatioDecimal.add(
374
- // redemptionReimbursementRatioDecimal,
375
- // );
376
-
377
- // const collateralRatio = Decimal(collateralRatioPercentage.getOnChainInt)
378
- // .div(OCD_DECIMAL_UNIT)
379
- // .div(100);
380
-
381
- // const maxAvailableAdaForRedemptionInclReimb = calculateTotalAdaForRedemption(
382
- // iasset,
383
- // iassetPrice,
384
- // lrpParams,
385
- // allLrps,
386
- // MAX_REDEMPTIONS_WITH_CDP_OPEN,
387
- // );
388
-
389
- // if (
390
- // collateralRatio.toNumber() <= 1 ||
391
- // baseCollateral <= 0n ||
392
- // maxAvailableAdaForRedemptionInclReimb <= 0n
393
- // ) {
394
- // return undefined;
395
- // }
396
-
397
- // // The leverage unconstrained by the liquidity in LRP
398
- // const theoreticalMaxLeverage = Decimal(Decimal(1).minus(totalFeeRatio))
399
- // .div(collateralRatio.minus(1).add(totalFeeRatio))
400
- // .add(1);
401
-
402
- // // b''
403
- // const bExFees = theoreticalMaxLeverage
404
- // .mul(baseCollateral)
405
- // .minus(baseCollateral)
406
- // .floor();
407
-
408
- // // b = b’’ / (1-f_r - f_m)
409
- // const b = bExFees.div(Decimal(1).minus(totalFeeRatio)).floor();
410
-
411
- // const cappedB = bigintMin(
412
- // maxAvailableAdaForRedemptionInclReimb,
413
- // fromDecimal(b),
414
- // );
415
-
416
- // const cappedBExFees = Decimal(cappedB)
417
- // .mul(Decimal(1).minus(totalFeeRatio))
418
- // .floor();
419
-
420
- // return Decimal(baseCollateral)
421
- // .add(cappedBExFees)
422
- // .div(baseCollateral)
423
- // .toNumber();
424
- // }
54
+ import { UTxO } from '@lucid-evolution/lucid';
55
+ import { OCD_DECIMAL_UNIT, OnChainDecimal } from '../../types/on-chain-decimal';
56
+ import { bigintMax, bigintMin, fromDecimal } from '../../utils/bigint-utils';
57
+ import { array as A, function as F } from 'fp-ts';
58
+ import { Decimal } from 'decimal.js';
59
+ import {
60
+ calculateTotalCollateralForRedemption,
61
+ robCollateralAmtToSpend,
62
+ } from '../rob/helpers';
63
+ import {
64
+ Rational,
65
+ rationalFloor,
66
+ rationalFromInt,
67
+ rationalMul,
68
+ } from '../../types/rational';
69
+ import { AssetClass } from '@3rd-eye-labs/cardano-offchain-common';
70
+ import { RobDatum } from '../rob/types-new';
71
+ import { calculateFeeFromRatio } from '../../utils/indigo-helpers';
72
+ import { iassetValueOfCollateral } from '../cdp/helpers';
73
+
74
+ /**
75
+ * How many LRP redemptions can we fit into a TX with CDP open.
76
+ */
77
+ export const MAX_REDEMPTIONS_WITH_CDP_OPEN = 4;
78
+
79
+ type ROBRedemptionDetails = {
80
+ utxo: UTxO;
81
+ redeemedCollateral: bigint;
82
+ /**
83
+ * The amount of iAssets paid to ROB.
84
+ */
85
+ iassetsPayoutAmt: bigint;
86
+ reimbursementIAssetAmt: bigint;
87
+ };
88
+
89
+ type ApproximateLeverageRedemptionsResult = {
90
+ leverage: number;
91
+ collateralRatio: Rational;
92
+ redeemedCollateral: bigint;
93
+ };
94
+
95
+ /**
96
+ * We assume exact precision. However, actual redemptions include rounding and
97
+ * the rounding behaviour changes based on the number of redemptions.
98
+ * This may slightly tweak the numbers and the result can be different.
99
+ *
100
+ * The math is described at the top of this code file.
101
+ */
102
+ export function approximateLeverageRedemptions(
103
+ baseCollateral: bigint,
104
+ targetLeverage: number,
105
+ redemptionReimbursementRatio: Rational,
106
+ debtMintingFeeRatio: Rational,
107
+ ): ApproximateLeverageRedemptionsResult {
108
+ const debtMintingFeeRatioDecimal = Decimal(debtMintingFeeRatio.numerator).div(
109
+ debtMintingFeeRatio.denominator,
110
+ );
111
+
112
+ const redemptionReimbursementRatioDecimal = Decimal(
113
+ redemptionReimbursementRatio.numerator,
114
+ ).div(redemptionReimbursementRatio.denominator);
115
+
116
+ const totalFeeRatio = debtMintingFeeRatioDecimal.add(
117
+ redemptionReimbursementRatioDecimal,
118
+ );
119
+
120
+ // b''
121
+ const bExFees = Decimal(baseCollateral)
122
+ .mul(targetLeverage)
123
+ .minus(baseCollateral)
124
+ .floor();
125
+
126
+ // b = b’’ / (1-f_r - f_m)
127
+ const b = bExFees.div(Decimal(1).minus(totalFeeRatio)).floor();
128
+
129
+ // c = (d + b * (1 - f_r - f_m)) / b
130
+ const collateralRatio: Rational = {
131
+ numerator: fromDecimal(Decimal(baseCollateral).add(bExFees)),
132
+ denominator: fromDecimal(b),
133
+ };
134
+
135
+ return {
136
+ leverage: targetLeverage,
137
+ collateralRatio: collateralRatio,
138
+ redeemedCollateral: fromDecimal(bExFees),
139
+ };
140
+ }
141
+
142
+ export function summarizeActualLeverageRedemptions(
143
+ lovelacesForRedemptionWithReimbursement: bigint,
144
+ redemptionReimbursementRatio: Rational,
145
+ iassetPrice: Rational,
146
+ // Picking from the beginning until the iasset redemption amount is satisfied.
147
+ redemptionLrps: [UTxO, RobDatum][],
148
+ ): {
149
+ redemptions: ROBRedemptionDetails[];
150
+ /**
151
+ * The actual amount received from redemptions (i.e. without the reimbursement fee).
152
+ */
153
+ totalRedeemedCollateral: bigint;
154
+ /**
155
+ * Total amount of IAssets to cover the reimbursement fee.
156
+ */
157
+ totalReimbursedIAsset: bigint;
158
+ /**
159
+ * Total amount of IAssets paid to ROBs, including the reimbursement.
160
+ */
161
+ totalIAssetPayout: bigint;
162
+ } {
163
+ const priceDecimal = Decimal(iassetPrice.numerator).div(
164
+ iassetPrice.denominator,
165
+ );
166
+
167
+ type Accumulator = {
168
+ /// The remaining collateral to spend from ROBs
169
+ remainingCollateralToSpend: bigint;
170
+ redemptions: ROBRedemptionDetails[];
171
+ };
172
+
173
+ const reimbRatio = Decimal(redemptionReimbursementRatio.numerator).div(
174
+ redemptionReimbursementRatio.denominator,
175
+ );
176
+
177
+ const redemptionDetails = F.pipe(
178
+ redemptionLrps,
179
+ A.reduce<[UTxO, RobDatum], Accumulator>(
180
+ {
181
+ remainingCollateralToSpend: lovelacesForRedemptionWithReimbursement,
182
+ redemptions: [],
183
+ },
184
+ (acc, lrp) => {
185
+ if (
186
+ acc.remainingCollateralToSpend <= 0n ||
187
+ iassetValueOfCollateral(
188
+ acc.remainingCollateralToSpend,
189
+ iassetPrice,
190
+ ) <= 0n
191
+ ) {
192
+ return acc;
193
+ }
194
+
195
+ const collateralToSpend = robCollateralAmtToSpend(lrp[0], lrp[1]);
196
+
197
+ if (collateralToSpend === 0n) {
198
+ return acc;
199
+ }
200
+
201
+ const newRemainingCollateral = bigintMax(
202
+ acc.remainingCollateralToSpend - collateralToSpend,
203
+ 0n,
204
+ );
205
+ const collateralToSpendInitial =
206
+ acc.remainingCollateralToSpend - newRemainingCollateral;
207
+
208
+ // TODO: make corrections here in case we can be ceiling this number instead.
209
+ // We want to be flooring here so that we don't go over the collateral to spend.
210
+ // Flooring multiple times is necessary here otherwise we could go over the collateral available.
211
+ // iassets = floor(collateral / price) / floor(1 - reimbursement fee)
212
+ const finalPayoutIAssets = fromDecimal(
213
+ Decimal(collateralToSpendInitial)
214
+ .div(priceDecimal)
215
+ .floor()
216
+ .div(Decimal(1).minus(reimbRatio))
217
+ .floor(),
218
+ );
219
+
220
+ const feeIAssetAmt = calculateFeeFromRatio(
221
+ redemptionReimbursementRatio,
222
+ finalPayoutIAssets,
223
+ );
224
+
225
+ // We need to calculate the new number since redemptionIAssets got corrected by rounding.
226
+ const finalCollateralToSpend = rationalFloor(
227
+ rationalMul(
228
+ rationalFromInt(finalPayoutIAssets - feeIAssetAmt),
229
+ iassetPrice,
230
+ ),
231
+ );
232
+
233
+ return {
234
+ remainingCollateralToSpend:
235
+ acc.remainingCollateralToSpend - finalCollateralToSpend,
236
+ redemptions: [
237
+ ...acc.redemptions,
238
+ {
239
+ utxo: lrp[0],
240
+ iassetsPayoutAmt: finalPayoutIAssets,
241
+ redeemedCollateral: finalCollateralToSpend,
242
+ reimbursementIAssetAmt: feeIAssetAmt,
243
+ },
244
+ ],
245
+ };
246
+ },
247
+ ),
248
+ );
249
+
250
+ const res = F.pipe(
251
+ redemptionDetails.redemptions,
252
+ A.reduce<
253
+ ROBRedemptionDetails,
254
+ {
255
+ redeemedCollateral: bigint;
256
+ payoutIAssets: bigint;
257
+ reimbursementIAssets: bigint;
258
+ }
259
+ >(
260
+ {
261
+ redeemedCollateral: 0n,
262
+ payoutIAssets: 0n,
263
+ reimbursementIAssets: 0n,
264
+ },
265
+ (acc, details) => {
266
+ return {
267
+ redeemedCollateral:
268
+ acc.redeemedCollateral + details.redeemedCollateral,
269
+ reimbursementIAssets:
270
+ acc.reimbursementIAssets + details.reimbursementIAssetAmt,
271
+ payoutIAssets: acc.payoutIAssets + details.iassetsPayoutAmt,
272
+ };
273
+ },
274
+ ),
275
+ );
276
+
277
+ return {
278
+ redemptions: redemptionDetails.redemptions,
279
+ totalRedeemedCollateral: res.redeemedCollateral,
280
+ totalReimbursedIAsset: res.reimbursementIAssets,
281
+ totalIAssetPayout: res.payoutIAssets,
282
+ };
283
+ }
284
+
285
+ /**
286
+ * The math is described at the top of this code file.
287
+ */
288
+ export function calculateCollateralRatioFromLeverage(
289
+ iasset: Uint8Array<ArrayBufferLike>,
290
+ collateralAsset: AssetClass,
291
+ leverage: number,
292
+ baseCollateral: bigint,
293
+ iassetPrice: Rational,
294
+ debtMintingFeePercentage: OnChainDecimal,
295
+ redemptionReimbursementPercentage: OnChainDecimal,
296
+ allLrps: [UTxO, RobDatum][],
297
+ ): OnChainDecimal | undefined {
298
+ const debtMintingFeeRatioDecimal = Decimal(
299
+ debtMintingFeePercentage.getOnChainInt,
300
+ )
301
+ .div(OCD_DECIMAL_UNIT)
302
+ .div(100);
303
+ const redemptionReimbursementRatioDecimal = Decimal(
304
+ redemptionReimbursementPercentage.getOnChainInt,
305
+ )
306
+ .div(OCD_DECIMAL_UNIT)
307
+ .div(100);
308
+
309
+ const totalFeeRatio = debtMintingFeeRatioDecimal.add(
310
+ redemptionReimbursementRatioDecimal,
311
+ );
312
+
313
+ const maxAvailableCollateralForRedemption =
314
+ calculateTotalCollateralForRedemption(
315
+ iasset,
316
+ collateralAsset,
317
+ iassetPrice,
318
+ allLrps,
319
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
320
+ );
321
+
322
+ if (
323
+ leverage <= 1 ||
324
+ baseCollateral <= 0n ||
325
+ maxAvailableCollateralForRedemption <= 0n
326
+ ) {
327
+ return undefined;
328
+ }
329
+
330
+ // b''
331
+ const bExFees = Decimal(baseCollateral)
332
+ .mul(leverage)
333
+ .minus(baseCollateral)
334
+ .floor();
335
+
336
+ // b = b’’ / (1-f_r - f_m)
337
+ const b = bExFees.div(Decimal(1).minus(totalFeeRatio)).floor();
338
+
339
+ const cappedB = bigintMin(
340
+ maxAvailableCollateralForRedemption,
341
+ fromDecimal(b),
342
+ );
343
+
344
+ const cappedBExFees = Decimal(cappedB)
345
+ .mul(Decimal(1).minus(totalFeeRatio))
346
+ .floor();
347
+
348
+ // c = (d + b * (1 - f_r - f_m)) / b
349
+ const collateralRatio = Decimal(
350
+ Decimal(baseCollateral).add(cappedBExFees),
351
+ ).div(cappedB);
352
+
353
+ return {
354
+ getOnChainInt: fromDecimal(
355
+ collateralRatio.mul(100n * OCD_DECIMAL_UNIT).floor(),
356
+ ),
357
+ };
358
+ }
359
+
360
+ /**
361
+ * The math is described at the top of this code file.
362
+ */
363
+ export function calculateLeverageFromCollateralRatio(
364
+ iasset: Uint8Array<ArrayBufferLike>,
365
+ collateralAsset: AssetClass,
366
+ collateralRatio: Rational,
367
+ baseCollateral: bigint,
368
+ iassetPrice: Rational,
369
+ debtMintingFeeRatio: Rational,
370
+ redemptionReimbursementRatio: Rational,
371
+ allLrps: [UTxO, RobDatum][],
372
+ ): number | undefined {
373
+ const debtMintingFeeRatioDecimal = Decimal(debtMintingFeeRatio.numerator).div(
374
+ debtMintingFeeRatio.denominator,
375
+ );
376
+ const redemptionReimbursementRatioDecimal = Decimal(
377
+ redemptionReimbursementRatio.numerator,
378
+ ).div(redemptionReimbursementRatio.denominator);
379
+
380
+ const totalFeeRatio = debtMintingFeeRatioDecimal.add(
381
+ redemptionReimbursementRatioDecimal,
382
+ );
383
+
384
+ const collateralRatioDecimal = Decimal(collateralRatio.numerator).div(
385
+ collateralRatio.denominator,
386
+ );
387
+
388
+ const maxAvailableCollateralForRedemption =
389
+ calculateTotalCollateralForRedemption(
390
+ iasset,
391
+ collateralAsset,
392
+ iassetPrice,
393
+ allLrps,
394
+ MAX_REDEMPTIONS_WITH_CDP_OPEN,
395
+ );
396
+
397
+ if (
398
+ collateralRatioDecimal.toNumber() <= 1 ||
399
+ baseCollateral <= 0n ||
400
+ maxAvailableCollateralForRedemption <= 0n
401
+ ) {
402
+ return undefined;
403
+ }
404
+
405
+ // The leverage unconstrained by the liquidity in LRP
406
+ const theoreticalMaxLeverage = Decimal(Decimal(1).minus(totalFeeRatio))
407
+ .div(collateralRatioDecimal.minus(1).add(totalFeeRatio))
408
+ .add(1);
409
+
410
+ // b''
411
+ const bExFees = theoreticalMaxLeverage
412
+ .mul(baseCollateral)
413
+ .minus(baseCollateral)
414
+ .floor();
415
+
416
+ // b = b’’ / (1-f_r - f_m)
417
+ const b = bExFees.div(Decimal(1).minus(totalFeeRatio)).floor();
418
+
419
+ const cappedB = bigintMin(
420
+ maxAvailableCollateralForRedemption,
421
+ fromDecimal(b),
422
+ );
423
+
424
+ const cappedBExFees = Decimal(cappedB)
425
+ .mul(Decimal(1).minus(totalFeeRatio))
426
+ .floor();
427
+
428
+ return Decimal(baseCollateral)
429
+ .add(cappedBExFees)
430
+ .div(baseCollateral)
431
+ .toNumber();
432
+ }