@indigo-labs/indigo-sdk 0.2.42 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/.github/workflows/ci.yml +4 -2
  2. package/dist/index.d.mts +3008 -2194
  3. package/dist/index.d.ts +3008 -2194
  4. package/dist/index.js +9827 -6194
  5. package/dist/index.mjs +8591 -4809
  6. package/package.json +14 -3
  7. package/src/contracts/cdp/helpers.ts +68 -72
  8. package/src/contracts/cdp/scripts.ts +50 -13
  9. package/src/contracts/cdp/transactions.ts +831 -545
  10. package/src/contracts/cdp/types-new.ts +256 -0
  11. package/src/contracts/cdp/types.ts +26 -144
  12. package/src/contracts/cdp-creator/scripts.ts +15 -9
  13. package/src/contracts/cdp-creator/types-new.ts +50 -0
  14. package/src/contracts/cdp-creator/types.ts +5 -31
  15. package/src/contracts/collector/scripts.ts +1 -1
  16. package/src/contracts/collector/transactions.ts +23 -13
  17. package/src/contracts/collector/types-new.ts +17 -0
  18. package/src/contracts/execute/scripts.ts +19 -10
  19. package/src/contracts/execute/types-new.ts +44 -0
  20. package/src/contracts/execute/types.ts +5 -38
  21. package/src/contracts/gov/helpers.ts +187 -51
  22. package/src/contracts/gov/scripts.ts +17 -10
  23. package/src/contracts/gov/transactions.ts +599 -271
  24. package/src/contracts/gov/types-new.ts +253 -100
  25. package/src/contracts/gov/types.ts +4 -71
  26. package/src/contracts/iasset/helpers.ts +172 -0
  27. package/src/contracts/iasset/scripts.ts +38 -0
  28. package/src/contracts/iasset/types.ts +154 -0
  29. package/src/contracts/initialize/actions.ts +768 -0
  30. package/src/contracts/initialize/helpers.ts +611 -36
  31. package/src/contracts/initialize/types.ts +102 -28
  32. package/src/contracts/interest-collection/helpers.ts +19 -0
  33. package/src/contracts/interest-collection/scripts.ts +44 -0
  34. package/src/contracts/interest-collection/transactions.ts +436 -0
  35. package/src/contracts/interest-collection/types-new.ts +50 -0
  36. package/src/contracts/interest-collection/types.ts +26 -0
  37. package/src/contracts/interest-oracle/helpers.ts +2 -30
  38. package/src/contracts/interest-oracle/scripts.ts +1 -1
  39. package/src/contracts/interest-oracle/transactions.ts +21 -16
  40. package/src/contracts/interest-oracle/types-new.ts +32 -0
  41. package/src/contracts/interest-oracle/types.ts +1 -40
  42. package/src/contracts/one-shot/transactions.ts +1 -2
  43. package/src/contracts/poll/helpers.ts +5 -23
  44. package/src/contracts/poll/scripts.ts +12 -13
  45. package/src/contracts/poll/types-poll-manager.ts +1 -19
  46. package/src/contracts/poll/types-poll-new.ts +170 -0
  47. package/src/contracts/poll/types-poll-shard.ts +2 -24
  48. package/src/contracts/price-oracle/helpers.ts +1 -4
  49. package/src/contracts/price-oracle/scripts.ts +3 -8
  50. package/src/contracts/price-oracle/transactions.ts +32 -25
  51. package/src/contracts/price-oracle/types-new.ts +50 -0
  52. package/src/contracts/price-oracle/types.ts +2 -36
  53. package/src/contracts/pyth-feed/helpers.ts +58 -0
  54. package/src/contracts/pyth-feed/scripts.ts +15 -0
  55. package/src/contracts/pyth-feed/types.ts +181 -0
  56. package/src/contracts/rob/helpers.ts +405 -0
  57. package/src/contracts/rob/scripts.ts +35 -0
  58. package/src/contracts/rob/transactions.ts +410 -0
  59. package/src/contracts/rob/types-new.ts +128 -0
  60. package/src/contracts/rob/types.ts +16 -0
  61. package/src/contracts/rob-leverage/helpers.ts +424 -0
  62. package/src/contracts/{leverage → rob-leverage}/transactions.ts +68 -48
  63. package/src/contracts/stability-pool/helpers.ts +714 -230
  64. package/src/contracts/stability-pool/scripts.ts +20 -15
  65. package/src/contracts/stability-pool/transactions.ts +625 -493
  66. package/src/contracts/stability-pool/types-new.ts +237 -100
  67. package/src/contracts/stability-pool/types.ts +5 -22
  68. package/src/contracts/stableswap/helpers.ts +22 -0
  69. package/src/contracts/stableswap/scripts.ts +37 -0
  70. package/src/contracts/stableswap/transactions.ts +647 -0
  71. package/src/contracts/stableswap/types-new.ts +131 -0
  72. package/src/contracts/stableswap/types.ts +17 -0
  73. package/src/contracts/staking/helpers.ts +49 -34
  74. package/src/contracts/staking/scripts.ts +1 -1
  75. package/src/contracts/staking/transactions.ts +85 -130
  76. package/src/contracts/staking/types-new.ts +60 -28
  77. package/src/contracts/staking/types.ts +1 -28
  78. package/src/contracts/treasury/helpers.ts +21 -0
  79. package/src/contracts/treasury/scripts.ts +16 -26
  80. package/src/contracts/treasury/transactions.ts +256 -27
  81. package/src/contracts/treasury/types-new.ts +69 -0
  82. package/src/contracts/treasury/types.ts +2 -43
  83. package/src/contracts/version-registry/scripts.ts +2 -2
  84. package/src/contracts/version-registry/types-new.ts +6 -7
  85. package/src/index.ts +37 -20
  86. package/src/scripts/auth-token-policy.ts +3 -2
  87. package/src/scripts/iasset-policy.ts +3 -2
  88. package/src/types/evolution-schema-options.ts +3 -3
  89. package/src/types/generic.ts +17 -89
  90. package/src/types/multisig.ts +48 -0
  91. package/src/types/on-chain-decimal.ts +14 -7
  92. package/src/types/rational.ts +61 -0
  93. package/src/types/system-params.ts +237 -41
  94. package/src/utils/array-utils.ts +70 -1
  95. package/src/utils/bigint-utils.ts +12 -0
  96. package/src/utils/indigo-helpers.ts +8 -10
  97. package/src/utils/lucid-utils.ts +47 -40
  98. package/src/utils/oracle-helpers.ts +62 -0
  99. package/src/utils/pyth/decode.ts +223 -0
  100. package/src/utils/pyth/encode.ts +262 -0
  101. package/src/utils/pyth/index.ts +14 -0
  102. package/src/utils/pyth/types.ts +87 -0
  103. package/src/validators/always-succeed-validator.ts +6 -0
  104. package/src/validators/cdp-creator-validator.ts +2 -2
  105. package/src/validators/cdp-redeem-validator.ts +7 -0
  106. package/src/validators/cdp-validator.ts +2 -2
  107. package/src/validators/collector-validator.ts +2 -2
  108. package/src/validators/execute-validator.ts +2 -2
  109. package/src/validators/governance-validator.ts +2 -2
  110. package/src/validators/iasset-validator.ts +7 -0
  111. package/src/validators/interest-collection-validator.ts +7 -0
  112. package/src/validators/interest-oracle-validator.ts +2 -2
  113. package/src/validators/poll-manager-validator.ts +2 -2
  114. package/src/validators/poll-shard-validator.ts +2 -2
  115. package/src/validators/price-oracle-validator.ts +7 -0
  116. package/src/validators/pyth-feed-validator.ts +7 -0
  117. package/src/validators/rob-validator.ts +7 -0
  118. package/src/validators/stability-pool-validator.ts +2 -2
  119. package/src/validators/stableswap-validator.ts +7 -0
  120. package/src/validators/staking-validator.ts +2 -2
  121. package/src/validators/treasury-validator.ts +2 -2
  122. package/src/validators/version-record-policy.ts +2 -2
  123. package/src/validators/version-registry-validator.ts +2 -2
  124. package/tests/always-succeed/script.ts +7 -0
  125. package/tests/bigint-utils.test.ts +41 -0
  126. package/tests/cdp/actions.ts +611 -0
  127. package/tests/cdp/cdp-helpers.ts +55 -0
  128. package/tests/cdp/cdp-queries.ts +440 -0
  129. package/tests/cdp/cdp.test.ts +6087 -0
  130. package/tests/cdp/transactions-mutated.ts +1729 -0
  131. package/tests/data/system-params.json +177 -34
  132. package/tests/datums.test.ts +209 -210
  133. package/tests/endpoints/initialize.ts +68 -0
  134. package/tests/endpoints/interest-collector.ts +37 -0
  135. package/tests/endpoints/treasury.ts +70 -0
  136. package/tests/gov/actions.ts +406 -0
  137. package/tests/gov/gov.test.ts +4450 -0
  138. package/tests/{queries → gov}/governance-queries.ts +6 -3
  139. package/tests/hash-checks.test.ts +38 -11
  140. package/tests/indigo-test-helpers.ts +100 -0
  141. package/tests/initialize.test.ts +61 -9
  142. package/tests/interest-collection/interest-collection.test.ts +892 -0
  143. package/tests/interest-collection/interest-collector-queries.ts +49 -0
  144. package/tests/interest-collection/transactions-mutated.ts +260 -0
  145. package/tests/interest-oracle.test.ts +43 -35
  146. package/tests/mock/assets-mock.ts +234 -23
  147. package/tests/mock/protocol-params-mock.ts +21 -0
  148. package/tests/price-oracle/actions.ts +163 -0
  149. package/tests/price-oracle/price-oracle-queries.ts +12 -0
  150. package/tests/price-oracle/price-oracle.test.ts +240 -0
  151. package/tests/price-oracle/transactions-mutated.ts +62 -0
  152. package/tests/pyth/endpoints.ts +96 -0
  153. package/tests/pyth/helpers.ts +37 -0
  154. package/tests/pyth/pyth-encoding.test.ts +376 -0
  155. package/tests/pyth/pyth-indigo.test.ts +509 -0
  156. package/tests/pyth/pyth.test.ts +300 -0
  157. package/tests/queries/execute-queries.ts +6 -5
  158. package/tests/queries/iasset-queries.ts +175 -5
  159. package/tests/queries/interest-oracle-queries.ts +4 -2
  160. package/tests/queries/poll-queries.ts +8 -9
  161. package/tests/queries/stability-pool-queries.ts +95 -48
  162. package/tests/queries/staking-queries.ts +4 -2
  163. package/tests/queries/treasury-queries.ts +80 -5
  164. package/tests/rob/actions.ts +58 -0
  165. package/tests/{lrp-leverage.test.ts → rob/rob-leverage.test.ts} +393 -296
  166. package/tests/rob/rob-queries.ts +95 -0
  167. package/tests/rob/rob.test.ts +3762 -0
  168. package/tests/rob/transactions-mutated.ts +853 -0
  169. package/tests/script-size.test.ts +240 -0
  170. package/tests/setup.ts +135 -0
  171. package/tests/stability-pool/actions.ts +210 -0
  172. package/tests/stability-pool.test.ts +5469 -666
  173. package/tests/stableswap/stableswap-actions.ts +84 -0
  174. package/tests/stableswap/stableswap-queries.ts +89 -0
  175. package/tests/stableswap/stableswap.test.ts +3891 -0
  176. package/tests/stableswap/transactions-mutated.ts +348 -0
  177. package/tests/staking.test.ts +82 -99
  178. package/tests/test-helpers.ts +58 -11
  179. package/tests/treasury.test.ts +242 -0
  180. package/tests/utils/asserts.ts +74 -0
  181. package/tests/utils/benchmark-utils.ts +81 -0
  182. package/tests/utils/index.ts +122 -4
  183. package/tsconfig.json +9 -1
  184. package/vitest.config.ts +3 -1
  185. package/src/contracts/collector/types.ts +0 -16
  186. package/src/contracts/initialize/transactions.ts +0 -891
  187. package/src/contracts/leverage/helpers.ts +0 -424
  188. package/src/contracts/lrp/helpers.ts +0 -294
  189. package/src/contracts/lrp/scripts.ts +0 -27
  190. package/src/contracts/lrp/transactions.ts +0 -250
  191. package/src/contracts/lrp/types.ts +0 -131
  192. package/src/contracts/poll/types-poll.ts +0 -88
  193. package/src/contracts/vesting/helpers.ts +0 -218
  194. package/src/utils/value-helpers.ts +0 -37
  195. package/src/validators/lrp-validator.ts +0 -7
  196. package/tests/cdp.test.ts +0 -1528
  197. package/tests/gov.test.ts +0 -2011
  198. package/tests/lrp.test.ts +0 -673
  199. package/tests/queries/cdp-queries.ts +0 -220
  200. package/tests/queries/lrp-queries.ts +0 -76
  201. package/tests/queries/price-oracle-queries.ts +0 -10
@@ -0,0 +1,3762 @@
1
+ import { assert, beforeEach, describe, expect, test } from 'vitest';
2
+ import {
3
+ Emulator,
4
+ EmulatorAccount,
5
+ fromHex,
6
+ fromText,
7
+ generateEmulatorAccount,
8
+ Lucid,
9
+ paymentCredentialOf,
10
+ addAssets,
11
+ } from '@lucid-evolution/lucid';
12
+ import {
13
+ adjustRob,
14
+ cancelRob,
15
+ claimRob,
16
+ openRob,
17
+ } from '../../src/contracts/rob/transactions';
18
+ import { findAllRobs, findSingleRob } from './rob-queries';
19
+ import { addrDetails } from '../../src/utils/lucid-utils';
20
+ import {
21
+ IndigoTestContext,
22
+ LucidContext,
23
+ repeat,
24
+ runAndAwaitTx,
25
+ runAndAwaitTxBuilder,
26
+ } from '../test-helpers';
27
+ import {
28
+ createProposal,
29
+ fromSystemParamsAsset,
30
+ MIN_ROB_COLLATERAL_AMT,
31
+ robAmountToSpend,
32
+ } from '../../src';
33
+ import { strictEqual } from 'assert';
34
+ import {
35
+ adaAssetClass,
36
+ AssetClass,
37
+ assetClassValueOf,
38
+ lovelacesAmt,
39
+ mkAssetsOf,
40
+ mkLovelacesOf,
41
+ } from '@3rd-eye-labs/cardano-offchain-common';
42
+ import {
43
+ iusdInitialAssetCfg,
44
+ mkBaseCollateralAsset,
45
+ } from '../mock/assets-mock';
46
+ import { init } from '../endpoints/initialize';
47
+ import {
48
+ findAllNecessaryOrefs,
49
+ findOwnCdpNew,
50
+ findPriceOracleFromCollateralAsset,
51
+ findRandomCdpCreatorNew,
52
+ } from '../cdp/cdp-queries';
53
+ import {
54
+ redeemWithCdpAdjust,
55
+ redeemWithCdpClose,
56
+ redeemWithCdpCreate,
57
+ testRedeemRob,
58
+ } from './transactions-mutated';
59
+ import { expectScriptFailure, expectValue } from '../utils/asserts';
60
+ import { findGov } from '../gov/governance-queries';
61
+ import {
62
+ findCollateralAsset,
63
+ findCollateralAssetNew,
64
+ findIAssetNew,
65
+ } from '../queries/iasset-queries';
66
+ import { processSuccessfulProposal } from '../gov/actions';
67
+ import { runOpenCdp } from '../cdp/actions';
68
+ import {
69
+ runFeedPriceToOracle,
70
+ waitForOracleExpiration,
71
+ } from '../price-oracle/actions';
72
+ import { runRedeemRob } from './actions';
73
+ import { calculateFeeFromRatio } from '../../src/utils/indigo-helpers';
74
+ import { benchmarkAndAwaitTx } from '../utils/benchmark-utils';
75
+ import { sendValueTo, totalValueAtAddress } from '../utils';
76
+ import { MAINNET_PROTOCOL_PARAMETERS } from '../indigo-test-helpers';
77
+ import {
78
+ Rational,
79
+ rationalDiv,
80
+ rationalFloor,
81
+ rationalFromInt,
82
+ rationalMul,
83
+ } from '../../src/types/rational';
84
+
85
+ type MyContext = LucidContext<{
86
+ admin: EmulatorAccount;
87
+ user1: EmulatorAccount;
88
+ user2: EmulatorAccount;
89
+ }>;
90
+
91
+ const collateralAssetA: AssetClass = {
92
+ currencySymbol: fromHex(
93
+ // random generated
94
+ 'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dea',
95
+ ),
96
+ tokenName: fromHex(fromText('A')),
97
+ };
98
+
99
+ describe('ROB', () => {
100
+ beforeEach<MyContext>(async (context: MyContext) => {
101
+ context.users = {
102
+ admin: generateEmulatorAccount(
103
+ addAssets(
104
+ mkLovelacesOf(100_000_000_000_000n),
105
+ mkAssetsOf(collateralAssetA, 10_000_000_000_000n),
106
+ ),
107
+ ),
108
+ user1: generateEmulatorAccount(
109
+ addAssets(
110
+ mkLovelacesOf(1_000_000_000_000n),
111
+ mkAssetsOf(collateralAssetA, 10_000_000_000_000n),
112
+ ),
113
+ ),
114
+ user2: generateEmulatorAccount(
115
+ addAssets(
116
+ mkLovelacesOf(1_000_000_000_000n),
117
+ mkAssetsOf(collateralAssetA, 10_000_000_000_000n),
118
+ ),
119
+ ),
120
+ };
121
+
122
+ context.emulator = new Emulator(
123
+ Object.values(context.users),
124
+ MAINNET_PROTOCOL_PARAMETERS,
125
+ );
126
+ context.lucid = await Lucid(context.emulator, 'Custom');
127
+ });
128
+
129
+ describe('Composite Txs', () => {
130
+ describe('redeem with CDP Adjust', () => {
131
+ test<MyContext>('ROB buy orders redeem with CDP deposit and mint (ADA case)', async (context: MyContext) => {
132
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
133
+
134
+ const [sysParams, [iusdAssetInfo]] = await init(
135
+ context.lucid,
136
+ [iusdInitialAssetCfg(0n)],
137
+ context.emulator.slot,
138
+ );
139
+
140
+ const robs_count = 8; // Adjusted for Pyth updates
141
+
142
+ await repeat(robs_count, async () => {
143
+ await runAndAwaitTx(
144
+ context.lucid,
145
+ openRob(
146
+ iusdAssetInfo.iassetTokenNameAscii,
147
+ 20_000_000n,
148
+ {
149
+ BuyIAssetOrder: {
150
+ collateralAsset: adaAssetClass,
151
+ maxPrice: rationalFromInt(1n),
152
+ },
153
+ },
154
+ context.lucid,
155
+ sysParams,
156
+ ),
157
+ );
158
+ });
159
+
160
+ context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
161
+
162
+ await runAndAwaitTx(
163
+ context.lucid,
164
+ runOpenCdp(
165
+ context,
166
+ sysParams,
167
+ iusdAssetInfo.iassetTokenNameAscii,
168
+ adaAssetClass,
169
+ 100_000_000n,
170
+ 500_000n,
171
+ ),
172
+ );
173
+
174
+ const orefs = await findAllNecessaryOrefs(
175
+ context.lucid,
176
+ sysParams,
177
+ iusdAssetInfo.iassetTokenNameAscii,
178
+ adaAssetClass,
179
+ );
180
+
181
+ const SINGLE_REDEMPTION_AMT = 1_000_000n;
182
+
183
+ const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
184
+ context.lucid,
185
+ orefs.collateralAsset,
186
+ );
187
+
188
+ await benchmarkAndAwaitTx(
189
+ `ROB composite - ${robs_count} buy orders redeem with CDP deposit and mint`,
190
+ await redeemWithCdpAdjust(
191
+ (
192
+ await findAllRobs(
193
+ context.lucid,
194
+ sysParams,
195
+ iusdAssetInfo.iassetTokenNameAscii,
196
+ )
197
+ ).map((rob) => [rob.utxo, SINGLE_REDEMPTION_AMT]),
198
+ 5n,
199
+ BigInt(robs_count) * SINGLE_REDEMPTION_AMT,
200
+ (await findOwnCdpNew(context.lucid, sysParams)).utxo,
201
+ orefs.iasset.utxo,
202
+ orefs.collateralAsset.utxo,
203
+ priceOracleUtxo!,
204
+ orefs.interestOracleUtxo,
205
+ orefs.treasuryUtxo,
206
+ orefs.interestCollectorUtxo,
207
+ context.emulator.slot,
208
+ context.lucid,
209
+ sysParams,
210
+ ),
211
+ context.lucid,
212
+ context.emulator,
213
+ );
214
+ });
215
+
216
+ test<MyContext>('ROB buy orders redeem with CDP deposit and mint (non ADA case)', async (context: MyContext) => {
217
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
218
+
219
+ const [sysParams, [iusdAssetInfo]] = await init(
220
+ context.lucid,
221
+ [
222
+ {
223
+ ...iusdInitialAssetCfg(),
224
+ collateralAssets: [mkBaseCollateralAsset(collateralAssetA, 0n)],
225
+ },
226
+ ],
227
+ context.emulator.slot,
228
+ );
229
+
230
+ const robs_count = 7; // Adjusted for Pyth updates
231
+
232
+ await repeat(robs_count, async () => {
233
+ await runAndAwaitTx(
234
+ context.lucid,
235
+ openRob(
236
+ iusdAssetInfo.iassetTokenNameAscii,
237
+ 20_000_000n,
238
+ {
239
+ BuyIAssetOrder: {
240
+ collateralAsset: collateralAssetA,
241
+ maxPrice: rationalFromInt(1n),
242
+ },
243
+ },
244
+ context.lucid,
245
+ sysParams,
246
+ ),
247
+ );
248
+ });
249
+
250
+ context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
251
+
252
+ const allCollateralInWallet = assetClassValueOf(
253
+ await totalValueAtAddress(context.lucid, context.users.user1.address),
254
+ collateralAssetA,
255
+ );
256
+
257
+ await runAndAwaitTx(
258
+ context.lucid,
259
+ runOpenCdp(
260
+ context,
261
+ sysParams,
262
+ iusdAssetInfo.iassetTokenNameAscii,
263
+ collateralAssetA,
264
+ allCollateralInWallet,
265
+ 500_000n,
266
+ ),
267
+ );
268
+
269
+ const orefs = await findAllNecessaryOrefs(
270
+ context.lucid,
271
+ sysParams,
272
+ iusdAssetInfo.iassetTokenNameAscii,
273
+ collateralAssetA,
274
+ );
275
+
276
+ const SINGLE_PAYOUT_IASSET_AMT = 1_000_000n;
277
+
278
+ const collateralAdjustment =
279
+ BigInt(robs_count) *
280
+ (SINGLE_PAYOUT_IASSET_AMT -
281
+ calculateFeeFromRatio(
282
+ (
283
+ await findIAssetNew(
284
+ context,
285
+ sysParams,
286
+ iusdAssetInfo.iassetTokenNameAscii,
287
+ )
288
+ ).datum.redemptionReimbursementRatio,
289
+ SINGLE_PAYOUT_IASSET_AMT,
290
+ ));
291
+
292
+ const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
293
+ context.lucid,
294
+ orefs.collateralAsset,
295
+ );
296
+
297
+ await benchmarkAndAwaitTx(
298
+ `ROB composite - ${robs_count} buy orders redeem with CDP deposit and mint (non ADA case)`,
299
+ await redeemWithCdpAdjust(
300
+ (
301
+ await findAllRobs(
302
+ context.lucid,
303
+ sysParams,
304
+ iusdAssetInfo.iassetTokenNameAscii,
305
+ )
306
+ ).map((rob) => [rob.utxo, SINGLE_PAYOUT_IASSET_AMT]),
307
+ // Since there's no more collateral in wallet (because all used as collateral before), this must be coming from redemptions.
308
+ collateralAdjustment,
309
+ BigInt(robs_count) * SINGLE_PAYOUT_IASSET_AMT,
310
+ (await findOwnCdpNew(context.lucid, sysParams)).utxo,
311
+ orefs.iasset.utxo,
312
+ orefs.collateralAsset.utxo,
313
+ priceOracleUtxo!,
314
+ orefs.interestOracleUtxo,
315
+ orefs.treasuryUtxo,
316
+ orefs.interestCollectorUtxo,
317
+ context.emulator.slot,
318
+ context.lucid,
319
+ sysParams,
320
+ ),
321
+ context.lucid,
322
+ context.emulator,
323
+ );
324
+
325
+ const res = await findOwnCdpNew(context.lucid, sysParams);
326
+
327
+ expect(
328
+ assetClassValueOf(res.utxo.assets, collateralAssetA),
329
+ 'Expected different CDP collateral',
330
+ ).toEqual(allCollateralInWallet + collateralAdjustment);
331
+ });
332
+
333
+ test<MyContext>('ROB sell orders redeem with CDP deposit and burn (ADA case)', async (context: MyContext) => {
334
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
335
+
336
+ const [sysParams, [iusdAssetInfo]] = await init(
337
+ context.lucid,
338
+ [iusdInitialAssetCfg(0n)],
339
+ context.emulator.slot,
340
+ );
341
+
342
+ const robs_count = 8;
343
+ const ROB_DEPOSIT = 20_000_000n;
344
+
345
+ await runAndAwaitTx(
346
+ context.lucid,
347
+ runOpenCdp(
348
+ context,
349
+ sysParams,
350
+ iusdAssetInfo.iassetTokenNameAscii,
351
+ adaAssetClass,
352
+ 1_000_000_000n,
353
+ ROB_DEPOSIT * BigInt(robs_count) +
354
+ // Should cover the minting fee
355
+ 1_000_000n,
356
+ ),
357
+ );
358
+
359
+ await repeat(robs_count, async () => {
360
+ await runAndAwaitTx(
361
+ context.lucid,
362
+ openRob(
363
+ iusdAssetInfo.iassetTokenNameAscii,
364
+ ROB_DEPOSIT,
365
+ {
366
+ SellIAssetOrder: {
367
+ allowedCollateralAssets: [
368
+ [adaAssetClass, rationalFromInt(1n)],
369
+ ],
370
+ },
371
+ },
372
+ context.lucid,
373
+ sysParams,
374
+ ),
375
+ );
376
+ });
377
+
378
+ context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
379
+
380
+ const INITIAL_MINT = 20_000_000n;
381
+
382
+ await runAndAwaitTx(
383
+ context.lucid,
384
+ runOpenCdp(
385
+ context,
386
+ sysParams,
387
+ iusdAssetInfo.iassetTokenNameAscii,
388
+ adaAssetClass,
389
+ 100_000_000n,
390
+ INITIAL_MINT,
391
+ ),
392
+ );
393
+
394
+ const orefs = await findAllNecessaryOrefs(
395
+ context.lucid,
396
+ sysParams,
397
+ iusdAssetInfo.iassetTokenNameAscii,
398
+ adaAssetClass,
399
+ );
400
+
401
+ const SINGLE_REDEMPTION_AMT = 1_000_000n;
402
+
403
+ const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
404
+ context.lucid,
405
+ orefs.collateralAsset,
406
+ );
407
+
408
+ await benchmarkAndAwaitTx(
409
+ `ROB composite - ${robs_count} sell orders redeem with CDP deposit and burn`,
410
+ await redeemWithCdpAdjust(
411
+ (
412
+ await findAllRobs(
413
+ context.lucid,
414
+ sysParams,
415
+ iusdAssetInfo.iassetTokenNameAscii,
416
+ )
417
+ ).map((rob) => [rob.utxo, SINGLE_REDEMPTION_AMT]),
418
+ 5n,
419
+ -BigInt(robs_count) * SINGLE_REDEMPTION_AMT,
420
+ (await findOwnCdpNew(context.lucid, sysParams)).utxo,
421
+ orefs.iasset.utxo,
422
+ orefs.collateralAsset.utxo,
423
+ priceOracleUtxo!,
424
+ orefs.interestOracleUtxo,
425
+ orefs.treasuryUtxo,
426
+ orefs.interestCollectorUtxo,
427
+ context.emulator.slot,
428
+ context.lucid,
429
+ sysParams,
430
+ ),
431
+ context.lucid,
432
+ context.emulator,
433
+ );
434
+
435
+ const ownCdp = await findOwnCdpNew(context.lucid, sysParams);
436
+
437
+ expect(ownCdp.datum.mintedAmt, 'unexpected minted amt').toEqual(
438
+ INITIAL_MINT - BigInt(robs_count) * SINGLE_REDEMPTION_AMT,
439
+ );
440
+ });
441
+
442
+ test<MyContext>('ROB sell orders redeem with CDP withdraw and burn (non ADA case)', async (context: MyContext) => {
443
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
444
+
445
+ const [sysParams, [iusdAssetInfo]] = await init(
446
+ context.lucid,
447
+ [
448
+ {
449
+ ...iusdInitialAssetCfg(),
450
+ collateralAssets: [mkBaseCollateralAsset(collateralAssetA, 0n)],
451
+ },
452
+ ],
453
+ context.emulator.slot,
454
+ );
455
+
456
+ const robs_count = 7; // TODO: Verify against Pyth feeds
457
+ const ROB_DEPOSIT = 20_000_000n;
458
+
459
+ await runAndAwaitTx(
460
+ context.lucid,
461
+ runOpenCdp(
462
+ context,
463
+ sysParams,
464
+ iusdAssetInfo.iassetTokenNameAscii,
465
+ collateralAssetA,
466
+ 1_000_000_000n,
467
+ ROB_DEPOSIT * BigInt(robs_count) +
468
+ // Should cover the minting fee
469
+ 1_000_000n,
470
+ ),
471
+ );
472
+
473
+ await repeat(robs_count, async () => {
474
+ await runAndAwaitTx(
475
+ context.lucid,
476
+ openRob(
477
+ iusdAssetInfo.iassetTokenNameAscii,
478
+ ROB_DEPOSIT,
479
+ {
480
+ SellIAssetOrder: {
481
+ allowedCollateralAssets: [
482
+ [collateralAssetA, rationalFromInt(1n)],
483
+ ],
484
+ },
485
+ },
486
+ context.lucid,
487
+ sysParams,
488
+ ),
489
+ );
490
+ });
491
+
492
+ context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
493
+
494
+ const allCollateralInWallet = assetClassValueOf(
495
+ await totalValueAtAddress(context.lucid, context.users.user1.address),
496
+ collateralAssetA,
497
+ );
498
+
499
+ const INITIAL_MINT = 20_000_000n;
500
+
501
+ await runAndAwaitTx(
502
+ context.lucid,
503
+ runOpenCdp(
504
+ context,
505
+ sysParams,
506
+ iusdAssetInfo.iassetTokenNameAscii,
507
+ collateralAssetA,
508
+ allCollateralInWallet,
509
+ INITIAL_MINT,
510
+ ),
511
+ );
512
+
513
+ const orefs = await findAllNecessaryOrefs(
514
+ context.lucid,
515
+ sysParams,
516
+ iusdAssetInfo.iassetTokenNameAscii,
517
+ collateralAssetA,
518
+ );
519
+
520
+ const SINGLE_REDEMPTION_AMT = 1_000_000n;
521
+ const reimbursementFee = calculateFeeFromRatio(
522
+ orefs.iasset.datum.redemptionReimbursementRatio,
523
+ SINGLE_REDEMPTION_AMT,
524
+ );
525
+
526
+ const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
527
+ context.lucid,
528
+ orefs.collateralAsset,
529
+ );
530
+
531
+ await benchmarkAndAwaitTx(
532
+ `ROB composite - ${robs_count} sell orders redeem with CDP withdraw and burn (non ADA case)`,
533
+ await redeemWithCdpAdjust(
534
+ (
535
+ await findAllRobs(
536
+ context.lucid,
537
+ sysParams,
538
+ iusdAssetInfo.iassetTokenNameAscii,
539
+ )
540
+ ).map((rob) => [rob.utxo, SINGLE_REDEMPTION_AMT]),
541
+ // Withdrawing the amount to pay for redemptions
542
+ -BigInt(robs_count) * (SINGLE_REDEMPTION_AMT + reimbursementFee),
543
+ // redeeming iassets, and using them to burn
544
+ -BigInt(robs_count) * SINGLE_REDEMPTION_AMT,
545
+ (await findOwnCdpNew(context.lucid, sysParams)).utxo,
546
+ orefs.iasset.utxo,
547
+ orefs.collateralAsset.utxo,
548
+ priceOracleUtxo!,
549
+ orefs.interestOracleUtxo,
550
+ orefs.treasuryUtxo,
551
+ orefs.interestCollectorUtxo,
552
+ context.emulator.slot,
553
+ context.lucid,
554
+ sysParams,
555
+ ),
556
+ context.lucid,
557
+ context.emulator,
558
+ );
559
+
560
+ const ownCdp = await findOwnCdpNew(context.lucid, sysParams);
561
+
562
+ expect(ownCdp.datum.mintedAmt, 'unexpected minted amt').toEqual(
563
+ INITIAL_MINT - BigInt(robs_count) * SINGLE_REDEMPTION_AMT,
564
+ );
565
+ expect(
566
+ assetClassValueOf(ownCdp.utxo.assets, collateralAssetA),
567
+ 'unexpected collateral amt',
568
+ ).toEqual(
569
+ allCollateralInWallet -
570
+ BigInt(robs_count) * (SINGLE_REDEMPTION_AMT + reimbursementFee),
571
+ );
572
+ });
573
+ });
574
+
575
+ describe('redeem with CDP close', () => {
576
+ test<MyContext>('ROB sell orders redeem with CDP close (ADA case)', async (context: MyContext) => {
577
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
578
+
579
+ const [sysParams, [iusdAssetInfo]] = await init(
580
+ context.lucid,
581
+ [iusdInitialAssetCfg(0n)],
582
+ context.emulator.slot,
583
+ );
584
+
585
+ const robs_count = 5;
586
+ const ROB_DEPOSIT = 20_000_000n;
587
+
588
+ await runAndAwaitTx(
589
+ context.lucid,
590
+ runOpenCdp(
591
+ context,
592
+ sysParams,
593
+ iusdAssetInfo.iassetTokenNameAscii,
594
+ adaAssetClass,
595
+ 1_000_000_000n,
596
+ ROB_DEPOSIT * BigInt(robs_count) +
597
+ // Should cover the minting fee
598
+ 1_000_000n,
599
+ ),
600
+ );
601
+
602
+ await repeat(robs_count, async () => {
603
+ await runAndAwaitTx(
604
+ context.lucid,
605
+ openRob(
606
+ iusdAssetInfo.iassetTokenNameAscii,
607
+ ROB_DEPOSIT,
608
+ {
609
+ SellIAssetOrder: {
610
+ allowedCollateralAssets: [
611
+ [adaAssetClass, rationalFromInt(1n)],
612
+ ],
613
+ },
614
+ },
615
+ context.lucid,
616
+ sysParams,
617
+ ),
618
+ );
619
+ });
620
+
621
+ context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
622
+
623
+ const SINGLE_PAYOUT_COLLATERAL_AMT = 1_000_000n;
624
+
625
+ const mintAmt =
626
+ BigInt(robs_count) *
627
+ (SINGLE_PAYOUT_COLLATERAL_AMT -
628
+ calculateFeeFromRatio(
629
+ (
630
+ await findIAssetNew(
631
+ context,
632
+ sysParams,
633
+ iusdAssetInfo.iassetTokenNameAscii,
634
+ )
635
+ ).datum.redemptionReimbursementRatio,
636
+ SINGLE_PAYOUT_COLLATERAL_AMT,
637
+ ));
638
+
639
+ await runAndAwaitTx(
640
+ context.lucid,
641
+ runOpenCdp(
642
+ context,
643
+ sysParams,
644
+ iusdAssetInfo.iassetTokenNameAscii,
645
+ adaAssetClass,
646
+ 100_000_000n,
647
+ mintAmt,
648
+ ),
649
+ );
650
+
651
+ const iassetAc = {
652
+ currencySymbol: fromHex(
653
+ sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
654
+ ),
655
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
656
+ };
657
+
658
+ await sendValueTo(
659
+ context.users.user2.address,
660
+ mkAssetsOf(
661
+ iassetAc,
662
+ assetClassValueOf(
663
+ await totalValueAtAddress(
664
+ context.lucid,
665
+ context.users.user1.address,
666
+ ),
667
+ iassetAc,
668
+ ),
669
+ ),
670
+ context.lucid,
671
+ );
672
+
673
+ const orefs = await findAllNecessaryOrefs(
674
+ context.lucid,
675
+ sysParams,
676
+ iusdAssetInfo.iassetTokenNameAscii,
677
+ adaAssetClass,
678
+ );
679
+
680
+ const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
681
+ context.lucid,
682
+ orefs.collateralAsset,
683
+ );
684
+
685
+ await benchmarkAndAwaitTx(
686
+ `ROB composite - ${robs_count} sell orders redeem with CDP close (ADA case)`,
687
+ await redeemWithCdpClose(
688
+ (
689
+ await findAllRobs(
690
+ context.lucid,
691
+ sysParams,
692
+ iusdAssetInfo.iassetTokenNameAscii,
693
+ )
694
+ ).map((rob) => [rob.utxo, SINGLE_PAYOUT_COLLATERAL_AMT]),
695
+ (await findOwnCdpNew(context.lucid, sysParams)).utxo,
696
+ orefs.iasset.utxo,
697
+ orefs.collateralAsset.utxo,
698
+ priceOracleUtxo!,
699
+ orefs.interestOracleUtxo,
700
+ orefs.interestCollectorUtxo,
701
+ sysParams,
702
+ context.lucid,
703
+ context.emulator.slot,
704
+ ),
705
+ context.lucid,
706
+ context.emulator,
707
+ );
708
+ });
709
+
710
+ test<MyContext>('ROB sell orders redeem with CDP close (non ADA case)', async (context: MyContext) => {
711
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
712
+
713
+ const [sysParams, [iusdAssetInfo]] = await init(
714
+ context.lucid,
715
+ [
716
+ {
717
+ ...iusdInitialAssetCfg(),
718
+ collateralAssets: [mkBaseCollateralAsset(collateralAssetA, 0n)],
719
+ },
720
+ ],
721
+ context.emulator.slot,
722
+ );
723
+
724
+ const robs_count = 4;
725
+ const ROB_DEPOSIT = 20_000_000n;
726
+
727
+ await runAndAwaitTx(
728
+ context.lucid,
729
+ runOpenCdp(
730
+ context,
731
+ sysParams,
732
+ iusdAssetInfo.iassetTokenNameAscii,
733
+ collateralAssetA,
734
+ 1_000_000_000n,
735
+ ROB_DEPOSIT * BigInt(robs_count) +
736
+ // Should cover the minting fee
737
+ 1_000_000n,
738
+ ),
739
+ );
740
+
741
+ await repeat(robs_count, async () => {
742
+ await runAndAwaitTx(
743
+ context.lucid,
744
+ openRob(
745
+ iusdAssetInfo.iassetTokenNameAscii,
746
+ ROB_DEPOSIT,
747
+ {
748
+ SellIAssetOrder: {
749
+ allowedCollateralAssets: [
750
+ [collateralAssetA, rationalFromInt(1n)],
751
+ ],
752
+ },
753
+ },
754
+ context.lucid,
755
+ sysParams,
756
+ ),
757
+ );
758
+ });
759
+
760
+ context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
761
+
762
+ const SINGLE_PAYOUT_COLLATERAL_AMT = 1_000_000n;
763
+
764
+ const mintAmt =
765
+ BigInt(robs_count) *
766
+ (SINGLE_PAYOUT_COLLATERAL_AMT -
767
+ calculateFeeFromRatio(
768
+ (
769
+ await findIAssetNew(
770
+ context,
771
+ sysParams,
772
+ iusdAssetInfo.iassetTokenNameAscii,
773
+ )
774
+ ).datum.redemptionReimbursementRatio,
775
+ SINGLE_PAYOUT_COLLATERAL_AMT,
776
+ ));
777
+
778
+ await runAndAwaitTx(
779
+ context.lucid,
780
+ runOpenCdp(
781
+ context,
782
+ sysParams,
783
+ iusdAssetInfo.iassetTokenNameAscii,
784
+ collateralAssetA,
785
+ 100_000_000n,
786
+ mintAmt,
787
+ ),
788
+ );
789
+
790
+ const iassetAc = {
791
+ currencySymbol: fromHex(
792
+ sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
793
+ ),
794
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
795
+ };
796
+
797
+ await sendValueTo(
798
+ context.users.user2.address,
799
+ mkAssetsOf(
800
+ iassetAc,
801
+ assetClassValueOf(
802
+ await totalValueAtAddress(
803
+ context.lucid,
804
+ context.users.user1.address,
805
+ ),
806
+ iassetAc,
807
+ ),
808
+ ),
809
+ context.lucid,
810
+ );
811
+
812
+ const orefs = await findAllNecessaryOrefs(
813
+ context.lucid,
814
+ sysParams,
815
+ iusdAssetInfo.iassetTokenNameAscii,
816
+ collateralAssetA,
817
+ );
818
+
819
+ const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
820
+ context.lucid,
821
+ orefs.collateralAsset,
822
+ );
823
+
824
+ await benchmarkAndAwaitTx(
825
+ `ROB composite - ${robs_count} sell orders redeem with CDP close (non ADA case)`,
826
+ await redeemWithCdpClose(
827
+ (
828
+ await findAllRobs(
829
+ context.lucid,
830
+ sysParams,
831
+ iusdAssetInfo.iassetTokenNameAscii,
832
+ )
833
+ ).map((rob) => [rob.utxo, SINGLE_PAYOUT_COLLATERAL_AMT]),
834
+ (await findOwnCdpNew(context.lucid, sysParams)).utxo,
835
+ orefs.iasset.utxo,
836
+ orefs.collateralAsset.utxo,
837
+ priceOracleUtxo!,
838
+ orefs.interestOracleUtxo,
839
+ orefs.interestCollectorUtxo,
840
+ sysParams,
841
+ context.lucid,
842
+ context.emulator.slot,
843
+ ),
844
+ context.lucid,
845
+ context.emulator,
846
+ );
847
+ });
848
+ });
849
+
850
+ describe('redeem with CDP create', () => {
851
+ test<MyContext>('ROB buy orders redeem with CDP create (ADA case)', async (context: MyContext) => {
852
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
853
+
854
+ const [sysParams, [iusdAssetInfo]] = await init(
855
+ context.lucid,
856
+ [iusdInitialAssetCfg(0n)],
857
+ context.emulator.slot,
858
+ );
859
+
860
+ const robs_count = 4;
861
+
862
+ await repeat(robs_count, async () => {
863
+ await runAndAwaitTx(
864
+ context.lucid,
865
+ openRob(
866
+ iusdAssetInfo.iassetTokenNameAscii,
867
+ 20_000_000n,
868
+ {
869
+ BuyIAssetOrder: {
870
+ collateralAsset: adaAssetClass,
871
+ maxPrice: rationalFromInt(1n),
872
+ },
873
+ },
874
+ context.lucid,
875
+ sysParams,
876
+ ),
877
+ );
878
+ });
879
+
880
+ context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
881
+
882
+ const orefs = await findAllNecessaryOrefs(
883
+ context.lucid,
884
+ sysParams,
885
+ iusdAssetInfo.iassetTokenNameAscii,
886
+ adaAssetClass,
887
+ );
888
+
889
+ const SINGLE_PAYOUT_IASSET_AMT = 1_000_000n;
890
+
891
+ const mintedAmt =
892
+ BigInt(robs_count) * SINGLE_PAYOUT_IASSET_AMT +
893
+ // This is extra that minting fee is paid from and user keeps what we validate after this Tx.
894
+ SINGLE_PAYOUT_IASSET_AMT;
895
+
896
+ const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
897
+ context.lucid,
898
+ orefs.collateralAsset,
899
+ );
900
+
901
+ await benchmarkAndAwaitTx(
902
+ `ROB composite - ${robs_count} buy orders redeem with CDP create (ADA case)`,
903
+ await redeemWithCdpCreate(
904
+ (
905
+ await findAllRobs(
906
+ context.lucid,
907
+ sysParams,
908
+ iusdAssetInfo.iassetTokenNameAscii,
909
+ )
910
+ ).map((rob) => [rob.utxo, SINGLE_PAYOUT_IASSET_AMT]),
911
+ 100_000_000n,
912
+ // everything that gets minted is sent to ROB
913
+ mintedAmt,
914
+ sysParams,
915
+ await findRandomCdpCreatorNew(context, sysParams),
916
+ orefs.iasset.utxo,
917
+ orefs.collateralAsset.utxo,
918
+ priceOracleUtxo!,
919
+ orefs.interestOracleUtxo,
920
+ orefs.treasuryUtxo,
921
+ context.lucid,
922
+ context.emulator.slot,
923
+ ),
924
+ context.lucid,
925
+ context.emulator,
926
+ );
927
+
928
+ expect(
929
+ assetClassValueOf(
930
+ await totalValueAtAddress(
931
+ context.lucid,
932
+ context.users.user1.address,
933
+ ),
934
+ {
935
+ currencySymbol: fromHex(
936
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
937
+ ),
938
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
939
+ },
940
+ ),
941
+ 'Unexpected iassets owned by user',
942
+ ).toEqual(
943
+ SINGLE_PAYOUT_IASSET_AMT -
944
+ calculateFeeFromRatio(
945
+ (
946
+ await findIAssetNew(
947
+ context,
948
+ sysParams,
949
+ iusdAssetInfo.iassetTokenNameAscii,
950
+ )
951
+ ).datum.debtMintingFeeRatio,
952
+ mintedAmt,
953
+ ),
954
+ );
955
+ });
956
+
957
+ test<MyContext>('ROB buy orders redeem with CDP create (non ADA case)', async (context: MyContext) => {
958
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
959
+
960
+ const [sysParams, [iusdAssetInfo]] = await init(
961
+ context.lucid,
962
+ [
963
+ {
964
+ ...iusdInitialAssetCfg(),
965
+ collateralAssets: [mkBaseCollateralAsset(collateralAssetA, 0n)],
966
+ },
967
+ ],
968
+ context.emulator.slot,
969
+ );
970
+
971
+ const robs_count = 3;
972
+
973
+ await repeat(robs_count, async () => {
974
+ await runAndAwaitTx(
975
+ context.lucid,
976
+ openRob(
977
+ iusdAssetInfo.iassetTokenNameAscii,
978
+ 20_000_000n,
979
+ {
980
+ BuyIAssetOrder: {
981
+ collateralAsset: collateralAssetA,
982
+ maxPrice: rationalFromInt(1n),
983
+ },
984
+ },
985
+ context.lucid,
986
+ sysParams,
987
+ ),
988
+ );
989
+ });
990
+
991
+ context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
992
+
993
+ const orefs = await findAllNecessaryOrefs(
994
+ context.lucid,
995
+ sysParams,
996
+ iusdAssetInfo.iassetTokenNameAscii,
997
+ collateralAssetA,
998
+ );
999
+
1000
+ const SINGLE_PAYOUT_IASSET_AMT = 1_000_000n;
1001
+
1002
+ const mintedAmt =
1003
+ BigInt(robs_count) * SINGLE_PAYOUT_IASSET_AMT +
1004
+ // This is extra that minting fee is paid from and user keeps what we validate after this Tx.
1005
+ SINGLE_PAYOUT_IASSET_AMT;
1006
+ const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
1007
+ context.lucid,
1008
+ orefs.collateralAsset,
1009
+ );
1010
+ await benchmarkAndAwaitTx(
1011
+ `ROB composite - ${robs_count} buy orders redeem with CDP create (non ADA case)`,
1012
+ await redeemWithCdpCreate(
1013
+ (
1014
+ await findAllRobs(
1015
+ context.lucid,
1016
+ sysParams,
1017
+ iusdAssetInfo.iassetTokenNameAscii,
1018
+ )
1019
+ ).map((rob) => [rob.utxo, SINGLE_PAYOUT_IASSET_AMT]),
1020
+ // use all as collateral
1021
+ assetClassValueOf(
1022
+ await totalValueAtAddress(
1023
+ context.lucid,
1024
+ context.users.user1.address,
1025
+ ),
1026
+ collateralAssetA,
1027
+ ),
1028
+ // everything that gets minted is sent to ROB
1029
+ mintedAmt,
1030
+ sysParams,
1031
+ await findRandomCdpCreatorNew(context, sysParams),
1032
+ orefs.iasset.utxo,
1033
+ orefs.collateralAsset.utxo,
1034
+ priceOracleUtxo!,
1035
+ orefs.interestOracleUtxo,
1036
+ orefs.treasuryUtxo,
1037
+ context.lucid,
1038
+ context.emulator.slot,
1039
+ ),
1040
+ context.lucid,
1041
+ context.emulator,
1042
+ );
1043
+
1044
+ const userVal = await totalValueAtAddress(
1045
+ context.lucid,
1046
+ context.users.user1.address,
1047
+ );
1048
+ expect(
1049
+ assetClassValueOf(userVal, {
1050
+ currencySymbol: fromHex(
1051
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
1052
+ ),
1053
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
1054
+ }),
1055
+ 'Unexpected iassets owned by user',
1056
+ ).toEqual(
1057
+ SINGLE_PAYOUT_IASSET_AMT -
1058
+ calculateFeeFromRatio(
1059
+ (
1060
+ await findIAssetNew(
1061
+ context,
1062
+ sysParams,
1063
+ iusdAssetInfo.iassetTokenNameAscii,
1064
+ )
1065
+ ).datum.debtMintingFeeRatio,
1066
+ mintedAmt,
1067
+ ),
1068
+ );
1069
+
1070
+ // Since everything was used as collateral, user owns only the amount after redemption.
1071
+ expect(
1072
+ assetClassValueOf(userVal, collateralAssetA),
1073
+ 'Unexpected collateral owned by user',
1074
+ ).toEqual(
1075
+ BigInt(robs_count) *
1076
+ (SINGLE_PAYOUT_IASSET_AMT -
1077
+ calculateFeeFromRatio(
1078
+ (
1079
+ await findIAssetNew(
1080
+ context,
1081
+ sysParams,
1082
+ iusdAssetInfo.iassetTokenNameAscii,
1083
+ )
1084
+ ).datum.redemptionReimbursementRatio,
1085
+ SINGLE_PAYOUT_IASSET_AMT,
1086
+ )),
1087
+ );
1088
+ });
1089
+ });
1090
+ });
1091
+
1092
+ test<MyContext>('adjust order type BUY positive and negative', async (context: MyContext) => {
1093
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1094
+
1095
+ const [sysParams, [iusdAssetInfo]] = await init(
1096
+ context.lucid,
1097
+ [iusdInitialAssetCfg()],
1098
+ context.emulator.slot,
1099
+ );
1100
+
1101
+ const [ownPkh, _] = await addrDetails(context.lucid);
1102
+
1103
+ await runAndAwaitTx(
1104
+ context.lucid,
1105
+ openRob(
1106
+ iusdAssetInfo.iassetTokenNameAscii,
1107
+ 20_000_000n,
1108
+ {
1109
+ BuyIAssetOrder: {
1110
+ collateralAsset: adaAssetClass,
1111
+ maxPrice: rationalFromInt(1n),
1112
+ },
1113
+ },
1114
+ context.lucid,
1115
+ sysParams,
1116
+ ),
1117
+ );
1118
+
1119
+ {
1120
+ const robBefore = await findSingleRob(
1121
+ context,
1122
+ sysParams,
1123
+ iusdAssetInfo.iassetTokenNameAscii,
1124
+ ownPkh,
1125
+ );
1126
+
1127
+ const amtToSpendBefore = robAmountToSpend(
1128
+ robBefore.utxo,
1129
+ robBefore.datum,
1130
+ sysParams,
1131
+ );
1132
+
1133
+ await runAndAwaitTx(
1134
+ context.lucid,
1135
+ adjustRob(
1136
+ context.lucid,
1137
+ robBefore.utxo,
1138
+ -1_000_000n,
1139
+ undefined,
1140
+ sysParams,
1141
+ ),
1142
+ );
1143
+
1144
+ const adjustedUtxo1 = await findSingleRob(
1145
+ context,
1146
+ sysParams,
1147
+ iusdAssetInfo.iassetTokenNameAscii,
1148
+ ownPkh,
1149
+ );
1150
+
1151
+ const amtToSpendAfter = robAmountToSpend(
1152
+ adjustedUtxo1.utxo,
1153
+ adjustedUtxo1.datum,
1154
+ sysParams,
1155
+ );
1156
+
1157
+ assert(amtToSpendBefore - amtToSpendAfter === 1_000_000n);
1158
+
1159
+ expect(
1160
+ lovelacesAmt(adjustedUtxo1.utxo.assets) >= amtToSpendAfter,
1161
+ 'Lovelaces to spend has to be smaller than actual lovelaces in UTXO',
1162
+ ).toBeTruthy();
1163
+ }
1164
+
1165
+ {
1166
+ const robBefore = await findSingleRob(
1167
+ context,
1168
+ sysParams,
1169
+ iusdAssetInfo.iassetTokenNameAscii,
1170
+ ownPkh,
1171
+ );
1172
+
1173
+ const amtToSpendBefore = robAmountToSpend(
1174
+ robBefore.utxo,
1175
+ robBefore.datum,
1176
+ sysParams,
1177
+ );
1178
+
1179
+ await runAndAwaitTx(
1180
+ context.lucid,
1181
+ adjustRob(
1182
+ context.lucid,
1183
+ robBefore.utxo,
1184
+ 5_000_000n,
1185
+ undefined,
1186
+ sysParams,
1187
+ ),
1188
+ );
1189
+
1190
+ const adjustedUtxo2 = await findSingleRob(
1191
+ context,
1192
+ sysParams,
1193
+ iusdAssetInfo.iassetTokenNameAscii,
1194
+ ownPkh,
1195
+ );
1196
+
1197
+ const amtToSpendAfter = robAmountToSpend(
1198
+ adjustedUtxo2.utxo,
1199
+ adjustedUtxo2.datum,
1200
+ sysParams,
1201
+ );
1202
+
1203
+ strictEqual(amtToSpendAfter - amtToSpendBefore, 5_000_000n);
1204
+
1205
+ expect(
1206
+ lovelacesAmt(adjustedUtxo2.utxo.assets) >= amtToSpendAfter,
1207
+ 'Lovelaces to spend has to be smaller than actual lovelaces in UTXO',
1208
+ ).toBeTruthy();
1209
+ }
1210
+ });
1211
+
1212
+ test<MyContext>('adjust order type SELL positive and negative', async (context: MyContext) => {
1213
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1214
+
1215
+ const [sysParams, [iusdAssetInfo]] = await init(
1216
+ context.lucid,
1217
+ [iusdInitialAssetCfg()],
1218
+ context.emulator.slot,
1219
+ );
1220
+
1221
+ const [ownPkh, _] = await addrDetails(context.lucid);
1222
+
1223
+ await runAndAwaitTx(
1224
+ context.lucid,
1225
+ runOpenCdp(
1226
+ context,
1227
+ sysParams,
1228
+ iusdAssetInfo.iassetTokenNameAscii,
1229
+ adaAssetClass,
1230
+ 100_000_000n,
1231
+ 30_000_000n,
1232
+ ),
1233
+ );
1234
+
1235
+ await runAndAwaitTx(
1236
+ context.lucid,
1237
+ openRob(
1238
+ iusdAssetInfo.iassetTokenNameAscii,
1239
+ 20_000_000n,
1240
+ {
1241
+ SellIAssetOrder: {
1242
+ allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
1243
+ },
1244
+ },
1245
+ context.lucid,
1246
+ sysParams,
1247
+ ),
1248
+ );
1249
+
1250
+ await runAndAwaitTx(
1251
+ context.lucid,
1252
+ findSingleRob(
1253
+ context,
1254
+ sysParams,
1255
+ iusdAssetInfo.iassetTokenNameAscii,
1256
+ ownPkh,
1257
+ ).then((rob) =>
1258
+ adjustRob(context.lucid, rob.utxo, -1_000_000n, undefined, sysParams),
1259
+ ),
1260
+ );
1261
+
1262
+ const adjustedUtxo1 = await findSingleRob(
1263
+ context,
1264
+ sysParams,
1265
+ iusdAssetInfo.iassetTokenNameAscii,
1266
+ ownPkh,
1267
+ );
1268
+
1269
+ const iassetAc: AssetClass = {
1270
+ currencySymbol: fromHex(
1271
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
1272
+ ),
1273
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
1274
+ };
1275
+
1276
+ assert(
1277
+ robAmountToSpend(adjustedUtxo1.utxo, adjustedUtxo1.datum, sysParams) ===
1278
+ 20_000_000n - 1_000_000n,
1279
+ );
1280
+
1281
+ expect(
1282
+ assetClassValueOf(adjustedUtxo1.utxo.assets, iassetAc) ===
1283
+ robAmountToSpend(adjustedUtxo1.utxo, adjustedUtxo1.datum, sysParams),
1284
+ 'IAssets to spend has to equal iassets in UTXO',
1285
+ ).toBeTruthy();
1286
+
1287
+ await runAndAwaitTx(
1288
+ context.lucid,
1289
+ adjustRob(
1290
+ context.lucid,
1291
+ adjustedUtxo1.utxo,
1292
+ 5_000_000n,
1293
+ undefined,
1294
+ sysParams,
1295
+ ),
1296
+ );
1297
+
1298
+ const adjustedUtxo2 = await findSingleRob(
1299
+ context,
1300
+ sysParams,
1301
+ iusdAssetInfo.iassetTokenNameAscii,
1302
+ ownPkh,
1303
+ );
1304
+
1305
+ const expectedResultAdaAmt = 20_000_000n - 1_000_000n + 5_000_000n;
1306
+
1307
+ strictEqual(
1308
+ robAmountToSpend(adjustedUtxo2.utxo, adjustedUtxo2.datum, sysParams),
1309
+ expectedResultAdaAmt,
1310
+ );
1311
+
1312
+ expect(
1313
+ assetClassValueOf(adjustedUtxo2.utxo.assets, iassetAc) >=
1314
+ robAmountToSpend(adjustedUtxo2.utxo, adjustedUtxo2.datum, sysParams),
1315
+ 'IAssets to spend has to equal iassets in UTXO',
1316
+ ).toBeTruthy();
1317
+ });
1318
+
1319
+ test<MyContext>('claim from BUY order type after a redemption', async (context: MyContext) => {
1320
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1321
+
1322
+ const [sysParams, [iusdAssetInfo]] = await init(
1323
+ context.lucid,
1324
+ [iusdInitialAssetCfg()],
1325
+ context.emulator.slot,
1326
+ );
1327
+
1328
+ const [ownPkh, _] = await addrDetails(context.lucid);
1329
+
1330
+ await runAndAwaitTx(
1331
+ context.lucid,
1332
+ runOpenCdp(
1333
+ context,
1334
+ sysParams,
1335
+ iusdAssetInfo.iassetTokenNameAscii,
1336
+ adaAssetClass,
1337
+ 100_000_000n,
1338
+ 30_000_000n,
1339
+ ),
1340
+ );
1341
+
1342
+ await runAndAwaitTx(
1343
+ context.lucid,
1344
+ openRob(
1345
+ iusdAssetInfo.iassetTokenNameAscii,
1346
+ 20_000_000n,
1347
+ {
1348
+ BuyIAssetOrder: {
1349
+ collateralAsset: adaAssetClass,
1350
+ maxPrice: rationalFromInt(1n),
1351
+ },
1352
+ },
1353
+ context.lucid,
1354
+ sysParams,
1355
+ ),
1356
+ );
1357
+
1358
+ const robUtxo = await findSingleRob(
1359
+ context,
1360
+ sysParams,
1361
+ iusdAssetInfo.iassetTokenNameAscii,
1362
+ ownPkh,
1363
+ );
1364
+
1365
+ const iassetAc: AssetClass = {
1366
+ currencySymbol: fromHex(
1367
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
1368
+ ),
1369
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
1370
+ };
1371
+
1372
+ expect(
1373
+ assetClassValueOf(robUtxo.utxo.assets, iassetAc),
1374
+ 'ROB should have no iassets before redemption',
1375
+ ).toBe(0n);
1376
+
1377
+ const redemptionIAssetAmt = 11_000_000n;
1378
+
1379
+ const amtToSpendBefore = robAmountToSpend(
1380
+ robUtxo.utxo,
1381
+ robUtxo.datum,
1382
+ sysParams,
1383
+ );
1384
+
1385
+ await runAndAwaitTx(
1386
+ context.lucid,
1387
+ runRedeemRob(
1388
+ context,
1389
+ sysParams,
1390
+ [[robUtxo, redemptionIAssetAmt]],
1391
+ iusdAssetInfo.iassetTokenNameAscii,
1392
+ adaAssetClass,
1393
+ context.emulator.slot,
1394
+ ),
1395
+ );
1396
+
1397
+ const redeemedRob = await findSingleRob(
1398
+ context,
1399
+ sysParams,
1400
+ iusdAssetInfo.iassetTokenNameAscii,
1401
+ ownPkh,
1402
+ );
1403
+
1404
+ expect(
1405
+ assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
1406
+ 'ROB has wrong number of iassets after redemption',
1407
+ ).toEqual(redemptionIAssetAmt);
1408
+
1409
+ expect(
1410
+ amtToSpendBefore -
1411
+ robAmountToSpend(redeemedRob.utxo, redeemedRob.datum, sysParams),
1412
+ 'ROB has wrong number redeemed',
1413
+ ).toEqual(
1414
+ // Since price is 1
1415
+ redemptionIAssetAmt -
1416
+ calculateFeeFromRatio(
1417
+ (
1418
+ await findIAssetNew(
1419
+ context,
1420
+ sysParams,
1421
+ iusdAssetInfo.iassetTokenNameAscii,
1422
+ )
1423
+ ).datum.redemptionReimbursementRatio,
1424
+ redemptionIAssetAmt,
1425
+ ),
1426
+ );
1427
+
1428
+ await runAndAwaitTx(
1429
+ context.lucid,
1430
+ claimRob(context.lucid, redeemedRob.utxo, sysParams),
1431
+ );
1432
+
1433
+ const claimedRob = await findSingleRob(
1434
+ context,
1435
+ sysParams,
1436
+ iusdAssetInfo.iassetTokenNameAscii,
1437
+ ownPkh,
1438
+ );
1439
+
1440
+ expect(
1441
+ assetClassValueOf(claimedRob.utxo.assets, iassetAc),
1442
+ 'ROB has to have 0 redemption assets after claim',
1443
+ ).toBe(0n);
1444
+ });
1445
+
1446
+ test<MyContext>('claim from BUY order type after a redemption when price is NOT 1:1', async (context: MyContext) => {
1447
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1448
+
1449
+ const [sysParams, [iusdAssetInfo]] = await init(
1450
+ context.lucid,
1451
+ [iusdInitialAssetCfg()],
1452
+ context.emulator.slot,
1453
+ );
1454
+
1455
+ const [ownPkh, _] = await addrDetails(context.lucid);
1456
+
1457
+ await runAndAwaitTx(
1458
+ context.lucid,
1459
+ runOpenCdp(
1460
+ context,
1461
+ sysParams,
1462
+ iusdAssetInfo.iassetTokenNameAscii,
1463
+ adaAssetClass,
1464
+ 100_000_000n,
1465
+ 30_000_000n,
1466
+ ),
1467
+ );
1468
+
1469
+ const initialDeposit = 20_000_000n;
1470
+
1471
+ await runAndAwaitTx(
1472
+ context.lucid,
1473
+ openRob(
1474
+ iusdAssetInfo.iassetTokenNameAscii,
1475
+ initialDeposit,
1476
+ {
1477
+ BuyIAssetOrder: {
1478
+ collateralAsset: adaAssetClass,
1479
+ maxPrice: rationalFromInt(1n),
1480
+ },
1481
+ },
1482
+ context.lucid,
1483
+ sysParams,
1484
+ ),
1485
+ );
1486
+
1487
+ const robUtxo = await findSingleRob(
1488
+ context,
1489
+ sysParams,
1490
+ iusdAssetInfo.iassetTokenNameAscii,
1491
+ ownPkh,
1492
+ );
1493
+
1494
+ const iassetAc: AssetClass = {
1495
+ currencySymbol: fromHex(
1496
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
1497
+ ),
1498
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
1499
+ };
1500
+
1501
+ expect(
1502
+ assetClassValueOf(robUtxo.utxo.assets, iassetAc),
1503
+ 'ROB should have no iassets before redemption',
1504
+ ).toBe(0n);
1505
+
1506
+ const newPrice: Rational = { numerator: 75n, denominator: 100n };
1507
+ await runFeedPriceToOracle(
1508
+ context,
1509
+ sysParams,
1510
+ iusdAssetInfo,
1511
+ adaAssetClass,
1512
+ newPrice,
1513
+ );
1514
+
1515
+ const redemptionIAssetAmt = 7_500_000n;
1516
+
1517
+ await runAndAwaitTx(
1518
+ context.lucid,
1519
+ runRedeemRob(
1520
+ context,
1521
+ sysParams,
1522
+ [[robUtxo, redemptionIAssetAmt]],
1523
+ iusdAssetInfo.iassetTokenNameAscii,
1524
+ adaAssetClass,
1525
+ context.emulator.slot,
1526
+ ),
1527
+ );
1528
+
1529
+ const redeemedRob = await findSingleRob(
1530
+ context,
1531
+ sysParams,
1532
+ iusdAssetInfo.iassetTokenNameAscii,
1533
+ ownPkh,
1534
+ );
1535
+
1536
+ const expectedRedeemedAmt = rationalFloor(
1537
+ rationalMul(
1538
+ rationalFromInt(
1539
+ redemptionIAssetAmt -
1540
+ calculateFeeFromRatio(
1541
+ (
1542
+ await findIAssetNew(
1543
+ context,
1544
+ sysParams,
1545
+ iusdAssetInfo.iassetTokenNameAscii,
1546
+ )
1547
+ ).datum.redemptionReimbursementRatio,
1548
+ redemptionIAssetAmt,
1549
+ ),
1550
+ ),
1551
+ newPrice,
1552
+ ),
1553
+ );
1554
+
1555
+ expect(
1556
+ robAmountToSpend(redeemedRob.utxo, redeemedRob.datum, sysParams),
1557
+ 'Wrong amt to spend in datum',
1558
+ ).toEqual(initialDeposit - expectedRedeemedAmt);
1559
+
1560
+ expectValue(
1561
+ redeemedRob.utxo.assets,
1562
+ 'Wrong value after redemption',
1563
+ ).toEqual(
1564
+ addAssets(
1565
+ mkAssetsOf(iassetAc, redemptionIAssetAmt),
1566
+ mkLovelacesOf(
1567
+ MIN_ROB_COLLATERAL_AMT + initialDeposit - expectedRedeemedAmt,
1568
+ ),
1569
+ ),
1570
+ );
1571
+
1572
+ await runAndAwaitTx(
1573
+ context.lucid,
1574
+ claimRob(context.lucid, redeemedRob.utxo, sysParams),
1575
+ );
1576
+
1577
+ const claimedRob = await findSingleRob(
1578
+ context,
1579
+ sysParams,
1580
+ iusdAssetInfo.iassetTokenNameAscii,
1581
+ ownPkh,
1582
+ );
1583
+
1584
+ expect(
1585
+ assetClassValueOf(claimedRob.utxo.assets, iassetAc),
1586
+ 'ROB has to have 0 redemption assets after claim',
1587
+ ).toBe(0n);
1588
+ });
1589
+
1590
+ test<MyContext>('claim from SELL order type after a redemption when price is NOT 1:1', async (context: MyContext) => {
1591
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1592
+
1593
+ const [sysParams, [iusdAssetInfo]] = await init(
1594
+ context.lucid,
1595
+ [iusdInitialAssetCfg()],
1596
+ context.emulator.slot,
1597
+ );
1598
+
1599
+ const [ownPkh, _] = await addrDetails(context.lucid);
1600
+
1601
+ await runAndAwaitTx(
1602
+ context.lucid,
1603
+ runOpenCdp(
1604
+ context,
1605
+ sysParams,
1606
+ iusdAssetInfo.iassetTokenNameAscii,
1607
+ adaAssetClass,
1608
+ 100_000_000n,
1609
+ 30_000_000n,
1610
+ ),
1611
+ );
1612
+
1613
+ const initialDeposit = 20_000_000n;
1614
+
1615
+ await runAndAwaitTx(
1616
+ context.lucid,
1617
+ openRob(
1618
+ iusdAssetInfo.iassetTokenNameAscii,
1619
+ initialDeposit,
1620
+ {
1621
+ SellIAssetOrder: {
1622
+ allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
1623
+ },
1624
+ },
1625
+ context.lucid,
1626
+ sysParams,
1627
+ ),
1628
+ );
1629
+
1630
+ const robUtxo = await findSingleRob(
1631
+ context,
1632
+ sysParams,
1633
+ iusdAssetInfo.iassetTokenNameAscii,
1634
+ ownPkh,
1635
+ );
1636
+
1637
+ const iassetAc: AssetClass = {
1638
+ currencySymbol: fromHex(
1639
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
1640
+ ),
1641
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
1642
+ };
1643
+
1644
+ expectValue(
1645
+ robUtxo.utxo.assets,
1646
+ 'Wrong ROB value before redemption',
1647
+ ).toEqual(
1648
+ addAssets(
1649
+ mkLovelacesOf(MIN_ROB_COLLATERAL_AMT),
1650
+ mkAssetsOf(iassetAc, initialDeposit),
1651
+ ),
1652
+ );
1653
+
1654
+ const newPrice: Rational = { numerator: 125n, denominator: 100n };
1655
+ await runFeedPriceToOracle(
1656
+ context,
1657
+ sysParams,
1658
+ iusdAssetInfo,
1659
+ adaAssetClass,
1660
+ newPrice,
1661
+ );
1662
+
1663
+ const payoutCollateralAmt = 7_500_000n;
1664
+
1665
+ await runAndAwaitTx(
1666
+ context.lucid,
1667
+ runRedeemRob(
1668
+ context,
1669
+ sysParams,
1670
+ [[robUtxo, payoutCollateralAmt]],
1671
+ iusdAssetInfo.iassetTokenNameAscii,
1672
+ adaAssetClass,
1673
+ context.emulator.slot,
1674
+ ),
1675
+ );
1676
+
1677
+ const redeemedRob = await findSingleRob(
1678
+ context,
1679
+ sysParams,
1680
+ iusdAssetInfo.iassetTokenNameAscii,
1681
+ ownPkh,
1682
+ );
1683
+
1684
+ const expectedRedemptionAmt = rationalFloor(
1685
+ rationalDiv(
1686
+ rationalFromInt(
1687
+ payoutCollateralAmt -
1688
+ calculateFeeFromRatio(
1689
+ (
1690
+ await findIAssetNew(
1691
+ context,
1692
+ sysParams,
1693
+ iusdAssetInfo.iassetTokenNameAscii,
1694
+ )
1695
+ ).datum.redemptionReimbursementRatio,
1696
+ payoutCollateralAmt,
1697
+ ),
1698
+ ),
1699
+ newPrice,
1700
+ ),
1701
+ );
1702
+
1703
+ expect(
1704
+ robAmountToSpend(redeemedRob.utxo, redeemedRob.datum, sysParams),
1705
+ 'Wrong amt to spend in datum',
1706
+ ).toEqual(initialDeposit - expectedRedemptionAmt);
1707
+
1708
+ expectValue(
1709
+ redeemedRob.utxo.assets,
1710
+ 'Wrong value after redemption',
1711
+ ).toEqual(
1712
+ addAssets(
1713
+ mkAssetsOf(adaAssetClass, MIN_ROB_COLLATERAL_AMT + payoutCollateralAmt),
1714
+ mkAssetsOf(iassetAc, initialDeposit - expectedRedemptionAmt),
1715
+ ),
1716
+ );
1717
+
1718
+ await runAndAwaitTx(
1719
+ context.lucid,
1720
+ claimRob(context.lucid, redeemedRob.utxo, sysParams),
1721
+ );
1722
+ });
1723
+
1724
+ test<MyContext>('redemption of BUY order type when limit price not met fails', async (context: MyContext) => {
1725
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1726
+
1727
+ const [sysParams, [iusdAssetInfo]] = await init(
1728
+ context.lucid,
1729
+ [iusdInitialAssetCfg()],
1730
+ context.emulator.slot,
1731
+ );
1732
+
1733
+ const [ownPkh, _] = await addrDetails(context.lucid);
1734
+
1735
+ await runAndAwaitTx(
1736
+ context.lucid,
1737
+ runOpenCdp(
1738
+ context,
1739
+ sysParams,
1740
+ iusdAssetInfo.iassetTokenNameAscii,
1741
+ adaAssetClass,
1742
+ 100_000_000n,
1743
+ 30_000_000n,
1744
+ ),
1745
+ );
1746
+
1747
+ await runAndAwaitTx(
1748
+ context.lucid,
1749
+ openRob(
1750
+ iusdAssetInfo.iassetTokenNameAscii,
1751
+ 20_000_000n,
1752
+ {
1753
+ BuyIAssetOrder: {
1754
+ collateralAsset: adaAssetClass,
1755
+ maxPrice: { numerator: 9n, denominator: 10n },
1756
+ },
1757
+ },
1758
+ context.lucid,
1759
+ sysParams,
1760
+ ),
1761
+ );
1762
+
1763
+ const robUtxo = await findSingleRob(
1764
+ context,
1765
+ sysParams,
1766
+ iusdAssetInfo.iassetTokenNameAscii,
1767
+ ownPkh,
1768
+ );
1769
+
1770
+ const iassetAc: AssetClass = {
1771
+ currencySymbol: fromHex(
1772
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
1773
+ ),
1774
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
1775
+ };
1776
+
1777
+ expect(
1778
+ assetClassValueOf(robUtxo.utxo.assets, iassetAc),
1779
+ 'ROB should have no iassets before redemption',
1780
+ ).toBe(0n);
1781
+
1782
+ await expectScriptFailure(
1783
+ 'Max price exceeded',
1784
+ runRedeemRob(
1785
+ context,
1786
+ sysParams,
1787
+ [[robUtxo, 11_000_000n]],
1788
+ iusdAssetInfo.iassetTokenNameAscii,
1789
+ adaAssetClass,
1790
+ context.emulator.slot,
1791
+ ),
1792
+ );
1793
+ });
1794
+
1795
+ test<MyContext>('claim ADA collateral from SELL order type after a redemption', async (context: MyContext) => {
1796
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1797
+
1798
+ const [sysParams, [iusdAssetInfo]] = await init(
1799
+ context.lucid,
1800
+ [iusdInitialAssetCfg()],
1801
+ context.emulator.slot,
1802
+ );
1803
+
1804
+ const [ownPkh, _] = await addrDetails(context.lucid);
1805
+
1806
+ await runAndAwaitTx(
1807
+ context.lucid,
1808
+ runOpenCdp(
1809
+ context,
1810
+ sysParams,
1811
+ iusdAssetInfo.iassetTokenNameAscii,
1812
+ adaAssetClass,
1813
+ 100_000_000n,
1814
+ 30_000_000n,
1815
+ ),
1816
+ );
1817
+
1818
+ await runAndAwaitTx(
1819
+ context.lucid,
1820
+ openRob(
1821
+ iusdAssetInfo.iassetTokenNameAscii,
1822
+ 20_000_000n,
1823
+ {
1824
+ SellIAssetOrder: {
1825
+ allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
1826
+ },
1827
+ },
1828
+ context.lucid,
1829
+ sysParams,
1830
+ ),
1831
+ );
1832
+
1833
+ const robUtxo = await findSingleRob(
1834
+ context,
1835
+ sysParams,
1836
+ iusdAssetInfo.iassetTokenNameAscii,
1837
+ ownPkh,
1838
+ );
1839
+
1840
+ const iassetAc: AssetClass = {
1841
+ currencySymbol: fromHex(
1842
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
1843
+ ),
1844
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
1845
+ };
1846
+
1847
+ const payoutCollateralAmt = 11_000_000n;
1848
+
1849
+ await runAndAwaitTx(
1850
+ context.lucid,
1851
+ runRedeemRob(
1852
+ context,
1853
+ sysParams,
1854
+ [[robUtxo, payoutCollateralAmt]],
1855
+ iusdAssetInfo.iassetTokenNameAscii,
1856
+ adaAssetClass,
1857
+ context.emulator.slot,
1858
+ ),
1859
+ );
1860
+
1861
+ const redeemedRob = await findSingleRob(
1862
+ context,
1863
+ sysParams,
1864
+ iusdAssetInfo.iassetTokenNameAscii,
1865
+ ownPkh,
1866
+ );
1867
+
1868
+ // Price is 1
1869
+ const expectedRedemptionAmt =
1870
+ payoutCollateralAmt -
1871
+ calculateFeeFromRatio(
1872
+ (
1873
+ await findIAssetNew(
1874
+ context,
1875
+ sysParams,
1876
+ iusdAssetInfo.iassetTokenNameAscii,
1877
+ )
1878
+ ).datum.redemptionReimbursementRatio,
1879
+ payoutCollateralAmt,
1880
+ );
1881
+
1882
+ expect(
1883
+ assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
1884
+ 'ROB has wrong number of iassets after redemption',
1885
+ ).toBe(20_000_000n - expectedRedemptionAmt);
1886
+
1887
+ await runAndAwaitTx(
1888
+ context.lucid,
1889
+ claimRob(context.lucid, redeemedRob.utxo, sysParams),
1890
+ );
1891
+
1892
+ const claimedRob = await findSingleRob(
1893
+ context,
1894
+ sysParams,
1895
+ iusdAssetInfo.iassetTokenNameAscii,
1896
+ ownPkh,
1897
+ );
1898
+
1899
+ expect(
1900
+ assetClassValueOf(claimedRob.utxo.assets, adaAssetClass),
1901
+ 'ROB has to have 0 redemption assets after claim',
1902
+ ).toBe(MIN_ROB_COLLATERAL_AMT);
1903
+ });
1904
+
1905
+ test<MyContext>('redemption of SELL order type when limit price not met fails', async (context: MyContext) => {
1906
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1907
+
1908
+ const [sysParams, [iusdAssetInfo]] = await init(
1909
+ context.lucid,
1910
+ [iusdInitialAssetCfg()],
1911
+ context.emulator.slot,
1912
+ );
1913
+
1914
+ const [ownPkh, _] = await addrDetails(context.lucid);
1915
+
1916
+ await runAndAwaitTx(
1917
+ context.lucid,
1918
+ runOpenCdp(
1919
+ context,
1920
+ sysParams,
1921
+ iusdAssetInfo.iassetTokenNameAscii,
1922
+ adaAssetClass,
1923
+ 100_000_000n,
1924
+ 30_000_000n,
1925
+ ),
1926
+ );
1927
+
1928
+ await runAndAwaitTx(
1929
+ context.lucid,
1930
+ openRob(
1931
+ iusdAssetInfo.iassetTokenNameAscii,
1932
+ 20_000_000n,
1933
+ {
1934
+ SellIAssetOrder: {
1935
+ allowedCollateralAssets: [
1936
+ [adaAssetClass, { numerator: 11n, denominator: 10n }],
1937
+ ],
1938
+ },
1939
+ },
1940
+ context.lucid,
1941
+ sysParams,
1942
+ ),
1943
+ );
1944
+
1945
+ const robUtxo = await findSingleRob(
1946
+ context,
1947
+ sysParams,
1948
+ iusdAssetInfo.iassetTokenNameAscii,
1949
+ ownPkh,
1950
+ );
1951
+
1952
+ await expectScriptFailure(
1953
+ 'Min price not satisfied',
1954
+ runRedeemRob(
1955
+ context,
1956
+ sysParams,
1957
+ [[robUtxo, 11_000_000n]],
1958
+ iusdAssetInfo.iassetTokenNameAscii,
1959
+ adaAssetClass,
1960
+ context.emulator.slot,
1961
+ ),
1962
+ );
1963
+ });
1964
+
1965
+ test<MyContext>('claim 2 collateral assets from SELL order type after 2 redemptions', async (context: MyContext) => {
1966
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
1967
+
1968
+ const [sysParams, [iusdAssetInfo]] = await init(
1969
+ context.lucid,
1970
+ [
1971
+ {
1972
+ ...iusdInitialAssetCfg(),
1973
+ collateralAssets: [
1974
+ mkBaseCollateralAsset(adaAssetClass, 0n),
1975
+ mkBaseCollateralAsset(collateralAssetA, 0n),
1976
+ ],
1977
+ },
1978
+ ],
1979
+ context.emulator.slot,
1980
+ );
1981
+
1982
+ const [ownPkh, _] = await addrDetails(context.lucid);
1983
+
1984
+ const iassetAC: AssetClass = {
1985
+ currencySymbol: fromHex(
1986
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
1987
+ ),
1988
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
1989
+ };
1990
+
1991
+ await runAndAwaitTx(
1992
+ context.lucid,
1993
+ runOpenCdp(
1994
+ context,
1995
+ sysParams,
1996
+ iusdAssetInfo.iassetTokenNameAscii,
1997
+ adaAssetClass,
1998
+ 100_000_000n,
1999
+ 30_000_000n,
2000
+ ),
2001
+ );
2002
+
2003
+ await runAndAwaitTx(
2004
+ context.lucid,
2005
+ openRob(
2006
+ iusdAssetInfo.iassetTokenNameAscii,
2007
+ 20_000_000n,
2008
+ {
2009
+ SellIAssetOrder: {
2010
+ allowedCollateralAssets: [
2011
+ [adaAssetClass, rationalFromInt(1n)],
2012
+ [collateralAssetA, rationalFromInt(1n)],
2013
+ ],
2014
+ },
2015
+ },
2016
+ context.lucid,
2017
+ sysParams,
2018
+ ),
2019
+ );
2020
+
2021
+ const payoutCollateralAmt = 2_000_000n;
2022
+
2023
+ const expectedRedemptionAmt =
2024
+ payoutCollateralAmt -
2025
+ calculateFeeFromRatio(
2026
+ (
2027
+ await findIAssetNew(
2028
+ context,
2029
+ sysParams,
2030
+ iusdAssetInfo.iassetTokenNameAscii,
2031
+ )
2032
+ ).datum.redemptionReimbursementRatio,
2033
+ payoutCollateralAmt,
2034
+ );
2035
+
2036
+ /**********
2037
+ * First redemption of ROB using ADA as payout
2038
+ ***********/
2039
+
2040
+ {
2041
+ await runAndAwaitTx(
2042
+ context.lucid,
2043
+ runRedeemRob(
2044
+ context,
2045
+ sysParams,
2046
+ [
2047
+ [
2048
+ await findSingleRob(
2049
+ context,
2050
+ sysParams,
2051
+ iusdAssetInfo.iassetTokenNameAscii,
2052
+ ownPkh,
2053
+ ),
2054
+ payoutCollateralAmt,
2055
+ ],
2056
+ ],
2057
+ iusdAssetInfo.iassetTokenNameAscii,
2058
+ adaAssetClass,
2059
+ context.emulator.slot,
2060
+ ),
2061
+ );
2062
+
2063
+ expect(
2064
+ assetClassValueOf(
2065
+ (
2066
+ await findSingleRob(
2067
+ context,
2068
+ sysParams,
2069
+ iusdAssetInfo.iassetTokenNameAscii,
2070
+ ownPkh,
2071
+ )
2072
+ ).utxo.assets,
2073
+ iassetAC,
2074
+ ),
2075
+ 'ROB has wrong number of iassets after redemption',
2076
+ ).toBe(20_000_000n - expectedRedemptionAmt);
2077
+ }
2078
+
2079
+ /**********
2080
+ * Second redemption of ROB using Collateral A as payout
2081
+ ***********/
2082
+
2083
+ {
2084
+ await runAndAwaitTx(
2085
+ context.lucid,
2086
+ runRedeemRob(
2087
+ context,
2088
+ sysParams,
2089
+ [
2090
+ [
2091
+ await findSingleRob(
2092
+ context,
2093
+ sysParams,
2094
+ iusdAssetInfo.iassetTokenNameAscii,
2095
+ ownPkh,
2096
+ ),
2097
+ payoutCollateralAmt,
2098
+ ],
2099
+ ],
2100
+ iusdAssetInfo.iassetTokenNameAscii,
2101
+ collateralAssetA,
2102
+ context.emulator.slot,
2103
+ ),
2104
+ );
2105
+
2106
+ expectValue(
2107
+ (
2108
+ await findSingleRob(
2109
+ context,
2110
+ sysParams,
2111
+ iusdAssetInfo.iassetTokenNameAscii,
2112
+ ownPkh,
2113
+ )
2114
+ ).utxo.assets,
2115
+ 'Wrong ROB value after 2 redemptions',
2116
+ ).toEqual(
2117
+ addAssets(
2118
+ mkLovelacesOf(MIN_ROB_COLLATERAL_AMT + payoutCollateralAmt),
2119
+ mkAssetsOf(collateralAssetA, payoutCollateralAmt),
2120
+ mkAssetsOf(iassetAC, 20_000_000n - 2n * expectedRedemptionAmt),
2121
+ ),
2122
+ );
2123
+ }
2124
+
2125
+ const redeemedRob = await findSingleRob(
2126
+ context,
2127
+ sysParams,
2128
+ iusdAssetInfo.iassetTokenNameAscii,
2129
+ ownPkh,
2130
+ );
2131
+
2132
+ await runAndAwaitTx(
2133
+ context.lucid,
2134
+ claimRob(context.lucid, redeemedRob.utxo, sysParams),
2135
+ );
2136
+
2137
+ const claimedRob = await findSingleRob(
2138
+ context,
2139
+ sysParams,
2140
+ iusdAssetInfo.iassetTokenNameAscii,
2141
+ ownPkh,
2142
+ );
2143
+
2144
+ expect(
2145
+ assetClassValueOf(claimedRob.utxo.assets, adaAssetClass),
2146
+ 'ROB has to have 0 redemption assets after claim',
2147
+ ).toBe(MIN_ROB_COLLATERAL_AMT);
2148
+ });
2149
+
2150
+ test<MyContext>('claim using adjust after a redemption', async (context: MyContext) => {
2151
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
2152
+
2153
+ const [sysParams, [iusdAssetInfo]] = await init(
2154
+ context.lucid,
2155
+ [iusdInitialAssetCfg()],
2156
+ context.emulator.slot,
2157
+ );
2158
+
2159
+ const [ownPkh, _] = await addrDetails(context.lucid);
2160
+
2161
+ await runAndAwaitTx(
2162
+ context.lucid,
2163
+ runOpenCdp(
2164
+ context,
2165
+ sysParams,
2166
+ iusdAssetInfo.iassetTokenNameAscii,
2167
+ adaAssetClass,
2168
+ 100_000_000n,
2169
+ 30_000_000n,
2170
+ ),
2171
+ );
2172
+
2173
+ await runAndAwaitTx(
2174
+ context.lucid,
2175
+ openRob(
2176
+ iusdAssetInfo.iassetTokenNameAscii,
2177
+ 20_000_000n,
2178
+ {
2179
+ BuyIAssetOrder: {
2180
+ collateralAsset: adaAssetClass,
2181
+ maxPrice: rationalFromInt(1n),
2182
+ },
2183
+ },
2184
+ context.lucid,
2185
+ sysParams,
2186
+ ),
2187
+ );
2188
+
2189
+ const robUtxo = await findSingleRob(
2190
+ context,
2191
+ sysParams,
2192
+ iusdAssetInfo.iassetTokenNameAscii,
2193
+ ownPkh,
2194
+ );
2195
+
2196
+ const iassetAc: AssetClass = {
2197
+ currencySymbol: fromHex(
2198
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
2199
+ ),
2200
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
2201
+ };
2202
+
2203
+ expect(
2204
+ assetClassValueOf(robUtxo.utxo.assets, iassetAc),
2205
+ 'ROB should have no iassets before redemption',
2206
+ ).toBe(0n);
2207
+
2208
+ const payoutIAssetAmt = 11_000_000n;
2209
+
2210
+ // const expectedRedemptionAmt =(
2211
+ // {
2212
+ // getOnChainInt:
2213
+ // payoutCollateralAmt -
2214
+ // calculateFeeFromPercentage(
2215
+ // (
2216
+ // await findIAssetNew(
2217
+ // context,
2218
+ // sysParams,
2219
+ // iusdAssetInfo.iassetTokenNameAscii,
2220
+ // )
2221
+ // ).datum.redemptionReimbursementPercentage,
2222
+ // payoutCollateralAmt,
2223
+ // ),
2224
+ // },
2225
+ // newPrice,
2226
+ // ).getOnChainInt;
2227
+
2228
+ await runAndAwaitTx(
2229
+ context.lucid,
2230
+ runRedeemRob(
2231
+ context,
2232
+ sysParams,
2233
+ [[robUtxo, payoutIAssetAmt]],
2234
+ iusdAssetInfo.iassetTokenNameAscii,
2235
+ adaAssetClass,
2236
+ context.emulator.slot,
2237
+ ),
2238
+ );
2239
+
2240
+ const redeemedRob = await findSingleRob(
2241
+ context,
2242
+ sysParams,
2243
+ iusdAssetInfo.iassetTokenNameAscii,
2244
+ ownPkh,
2245
+ );
2246
+
2247
+ expect(
2248
+ assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
2249
+ 'ROB has wrong number of iassets after redemption',
2250
+ ).toBe(payoutIAssetAmt);
2251
+
2252
+ await runAndAwaitTx(
2253
+ context.lucid,
2254
+ adjustRob(
2255
+ context.lucid,
2256
+ redeemedRob.utxo,
2257
+ -1_000_000n,
2258
+ undefined,
2259
+ sysParams,
2260
+ ),
2261
+ );
2262
+
2263
+ const adjustedRob = await findSingleRob(
2264
+ context,
2265
+ sysParams,
2266
+ iusdAssetInfo.iassetTokenNameAscii,
2267
+ ownPkh,
2268
+ );
2269
+
2270
+ expect(
2271
+ assetClassValueOf(adjustedRob.utxo.assets, iassetAc),
2272
+ 'ROB has to have 0 redemption assets after adjust',
2273
+ ).toBe(0n);
2274
+
2275
+ // Since price is 1
2276
+ expect(
2277
+ robAmountToSpend(adjustedRob.utxo, adjustedRob.datum, sysParams),
2278
+ ).toEqual(
2279
+ // 20mil start, -1mil adjusted, rest redeemed
2280
+ 20_000_000n -
2281
+ 1_000_000n -
2282
+ payoutIAssetAmt +
2283
+ calculateFeeFromRatio(
2284
+ (
2285
+ await findIAssetNew(
2286
+ context,
2287
+ sysParams,
2288
+ iusdAssetInfo.iassetTokenNameAscii,
2289
+ )
2290
+ ).datum.redemptionReimbursementRatio,
2291
+ payoutIAssetAmt,
2292
+ ),
2293
+ );
2294
+ });
2295
+
2296
+ test<MyContext>('single redemption and cancel', async (context: MyContext) => {
2297
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
2298
+
2299
+ const [sysParams, [iusdAssetInfo]] = await init(
2300
+ context.lucid,
2301
+ [iusdInitialAssetCfg()],
2302
+ context.emulator.slot,
2303
+ );
2304
+
2305
+ const [ownPkh, _] = await addrDetails(context.lucid);
2306
+
2307
+ await runAndAwaitTx(
2308
+ context.lucid,
2309
+ runOpenCdp(
2310
+ context,
2311
+ sysParams,
2312
+ iusdAssetInfo.iassetTokenNameAscii,
2313
+ adaAssetClass,
2314
+ 100_000_000n,
2315
+ 30_000_000n,
2316
+ ),
2317
+ );
2318
+
2319
+ await runAndAwaitTx(
2320
+ context.lucid,
2321
+ openRob(
2322
+ iusdAssetInfo.iassetTokenNameAscii,
2323
+ 20_000_000n,
2324
+ {
2325
+ BuyIAssetOrder: {
2326
+ collateralAsset: adaAssetClass,
2327
+ maxPrice: rationalFromInt(1n),
2328
+ },
2329
+ },
2330
+ context.lucid,
2331
+ sysParams,
2332
+ ),
2333
+ );
2334
+
2335
+ const robUtxo = await findSingleRob(
2336
+ context,
2337
+ sysParams,
2338
+ iusdAssetInfo.iassetTokenNameAscii,
2339
+ ownPkh,
2340
+ );
2341
+
2342
+ const iassetAc: AssetClass = {
2343
+ currencySymbol: fromHex(
2344
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
2345
+ ),
2346
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
2347
+ };
2348
+
2349
+ expect(
2350
+ assetClassValueOf(robUtxo.utxo.assets, iassetAc),
2351
+ 'ROB should have no iassets before redemption',
2352
+ ).toBe(0n);
2353
+
2354
+ const payoutIAssetAmt = 11_000_000n;
2355
+
2356
+ await runAndAwaitTx(
2357
+ context.lucid,
2358
+ runRedeemRob(
2359
+ context,
2360
+ sysParams,
2361
+ [[robUtxo, payoutIAssetAmt]],
2362
+ iusdAssetInfo.iassetTokenNameAscii,
2363
+ adaAssetClass,
2364
+ context.emulator.slot,
2365
+ ),
2366
+ );
2367
+
2368
+ const redeemedRob = await findSingleRob(
2369
+ context,
2370
+ sysParams,
2371
+ iusdAssetInfo.iassetTokenNameAscii,
2372
+ ownPkh,
2373
+ );
2374
+
2375
+ expect(
2376
+ assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
2377
+ 'ROB has wrong number of iassets after redemption',
2378
+ ).toBe(payoutIAssetAmt);
2379
+
2380
+ await runAndAwaitTx(
2381
+ context.lucid,
2382
+ cancelRob(redeemedRob.utxo, sysParams, context.lucid),
2383
+ );
2384
+ });
2385
+
2386
+ test<MyContext>('redeem, redeem again and cancel', async (context: MyContext) => {
2387
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
2388
+
2389
+ const [sysParams, [iusdAssetInfo]] = await init(
2390
+ context.lucid,
2391
+ [iusdInitialAssetCfg()],
2392
+ context.emulator.slot,
2393
+ );
2394
+
2395
+ const [ownPkh, _] = await addrDetails(context.lucid);
2396
+
2397
+ await runAndAwaitTx(
2398
+ context.lucid,
2399
+ runOpenCdp(
2400
+ context,
2401
+ sysParams,
2402
+ iusdAssetInfo.iassetTokenNameAscii,
2403
+ adaAssetClass,
2404
+ 100_000_000n,
2405
+ 30_000_000n,
2406
+ ),
2407
+ );
2408
+
2409
+ await runAndAwaitTx(
2410
+ context.lucid,
2411
+ openRob(
2412
+ iusdAssetInfo.iassetTokenNameAscii,
2413
+ 40_000_000n,
2414
+ {
2415
+ BuyIAssetOrder: {
2416
+ collateralAsset: adaAssetClass,
2417
+ maxPrice: rationalFromInt(1n),
2418
+ },
2419
+ },
2420
+ context.lucid,
2421
+ sysParams,
2422
+ ),
2423
+ );
2424
+
2425
+ const robUtxo = await findSingleRob(
2426
+ context,
2427
+ sysParams,
2428
+ iusdAssetInfo.iassetTokenNameAscii,
2429
+ ownPkh,
2430
+ );
2431
+
2432
+ const iassetAc: AssetClass = {
2433
+ currencySymbol: fromHex(
2434
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
2435
+ ),
2436
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
2437
+ };
2438
+
2439
+ expect(
2440
+ assetClassValueOf(robUtxo.utxo.assets, iassetAc),
2441
+ 'ROB should have no iassets before redemption',
2442
+ ).toBe(0n);
2443
+
2444
+ const payoutIAssetAmt = 11_000_000n;
2445
+
2446
+ await runAndAwaitTx(
2447
+ context.lucid,
2448
+ runRedeemRob(
2449
+ context,
2450
+ sysParams,
2451
+ [[robUtxo, payoutIAssetAmt]],
2452
+ iusdAssetInfo.iassetTokenNameAscii,
2453
+ adaAssetClass,
2454
+ context.emulator.slot,
2455
+ ),
2456
+ );
2457
+
2458
+ const redeemedRob = await findSingleRob(
2459
+ context,
2460
+ sysParams,
2461
+ iusdAssetInfo.iassetTokenNameAscii,
2462
+ ownPkh,
2463
+ );
2464
+
2465
+ expect(
2466
+ assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
2467
+ 'ROB has wrong number of iassets after redemption',
2468
+ ).toBe(payoutIAssetAmt);
2469
+
2470
+ await runAndAwaitTx(
2471
+ context.lucid,
2472
+ runRedeemRob(
2473
+ context,
2474
+ sysParams,
2475
+ [[redeemedRob, payoutIAssetAmt]],
2476
+ iusdAssetInfo.iassetTokenNameAscii,
2477
+ adaAssetClass,
2478
+ context.emulator.slot,
2479
+ ),
2480
+ );
2481
+
2482
+ const closableRob = await findSingleRob(
2483
+ context,
2484
+ sysParams,
2485
+ iusdAssetInfo.iassetTokenNameAscii,
2486
+ ownPkh,
2487
+ );
2488
+
2489
+ strictEqual(
2490
+ assetClassValueOf(closableRob.utxo.assets, iassetAc),
2491
+ payoutIAssetAmt * 2n,
2492
+ 'ROB has wrong number of iassets after 2 redemptions',
2493
+ );
2494
+
2495
+ await runAndAwaitTx(
2496
+ context.lucid,
2497
+ cancelRob(closableRob.utxo, sysParams, context.lucid),
2498
+ );
2499
+ });
2500
+
2501
+ test<MyContext>('multi redemption buy ROBs case (2 ROBs)', async (context: MyContext) => {
2502
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
2503
+
2504
+ const [sysParams, [iusdAssetInfo]] = await init(
2505
+ context.lucid,
2506
+ [iusdInitialAssetCfg()],
2507
+ context.emulator.slot,
2508
+ );
2509
+
2510
+ await runAndAwaitTx(
2511
+ context.lucid,
2512
+ runOpenCdp(
2513
+ context,
2514
+ sysParams,
2515
+ iusdAssetInfo.iassetTokenNameAscii,
2516
+ adaAssetClass,
2517
+ 100_000_000n,
2518
+ 30_000_000n,
2519
+ ),
2520
+ );
2521
+
2522
+ const initialDeposit = 20_000_000n;
2523
+
2524
+ await runAndAwaitTx(
2525
+ context.lucid,
2526
+ openRob(
2527
+ iusdAssetInfo.iassetTokenNameAscii,
2528
+ initialDeposit,
2529
+ {
2530
+ BuyIAssetOrder: {
2531
+ collateralAsset: adaAssetClass,
2532
+ maxPrice: rationalFromInt(1n),
2533
+ },
2534
+ },
2535
+ context.lucid,
2536
+ sysParams,
2537
+ ),
2538
+ );
2539
+
2540
+ context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
2541
+
2542
+ await runAndAwaitTx(
2543
+ context.lucid,
2544
+ openRob(
2545
+ iusdAssetInfo.iassetTokenNameAscii,
2546
+ initialDeposit,
2547
+ {
2548
+ BuyIAssetOrder: {
2549
+ collateralAsset: adaAssetClass,
2550
+ maxPrice: rationalFromInt(1n),
2551
+ },
2552
+ },
2553
+ context.lucid,
2554
+ sysParams,
2555
+ ),
2556
+ );
2557
+
2558
+ const robUtxo1 = await findSingleRob(
2559
+ context,
2560
+ sysParams,
2561
+ iusdAssetInfo.iassetTokenNameAscii,
2562
+ paymentCredentialOf(context.users.admin.address),
2563
+ );
2564
+ const robUtxo2 = await findSingleRob(
2565
+ context,
2566
+ sysParams,
2567
+ iusdAssetInfo.iassetTokenNameAscii,
2568
+ paymentCredentialOf(context.users.user1.address),
2569
+ );
2570
+
2571
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
2572
+
2573
+ await runAndAwaitTx(
2574
+ context.lucid,
2575
+ runRedeemRob(
2576
+ context,
2577
+ sysParams,
2578
+ [
2579
+ [robUtxo1, 10_000_000n],
2580
+ [robUtxo2, 11_000_000n],
2581
+ ],
2582
+ iusdAssetInfo.iassetTokenNameAscii,
2583
+ adaAssetClass,
2584
+ context.emulator.slot,
2585
+ ),
2586
+ );
2587
+
2588
+ const resultRobUtxo1 = await findSingleRob(
2589
+ context,
2590
+ sysParams,
2591
+ iusdAssetInfo.iassetTokenNameAscii,
2592
+ paymentCredentialOf(context.users.admin.address),
2593
+ );
2594
+ const resultRobUtxo2 = await findSingleRob(
2595
+ context,
2596
+ sysParams,
2597
+ iusdAssetInfo.iassetTokenNameAscii,
2598
+ paymentCredentialOf(context.users.user1.address),
2599
+ );
2600
+
2601
+ const iassetAc: AssetClass = {
2602
+ currencySymbol: fromHex(
2603
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
2604
+ ),
2605
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
2606
+ };
2607
+
2608
+ expectValue(resultRobUtxo1.utxo.assets, 'Wrong ROB1 value').toEqual(
2609
+ addAssets(
2610
+ mkLovelacesOf(
2611
+ MIN_ROB_COLLATERAL_AMT +
2612
+ initialDeposit -
2613
+ 10_000_000n +
2614
+ calculateFeeFromRatio(
2615
+ (
2616
+ await findIAssetNew(
2617
+ context,
2618
+ sysParams,
2619
+ iusdAssetInfo.iassetTokenNameAscii,
2620
+ )
2621
+ ).datum.redemptionReimbursementRatio,
2622
+ 10_000_000n,
2623
+ ),
2624
+ ),
2625
+ mkAssetsOf(iassetAc, 10_000_000n),
2626
+ ),
2627
+ );
2628
+ expectValue(resultRobUtxo2.utxo.assets, 'Wrong ROB2 value').toEqual(
2629
+ addAssets(
2630
+ mkLovelacesOf(
2631
+ MIN_ROB_COLLATERAL_AMT +
2632
+ initialDeposit -
2633
+ 11_000_000n +
2634
+ calculateFeeFromRatio(
2635
+ (
2636
+ await findIAssetNew(
2637
+ context,
2638
+ sysParams,
2639
+ iusdAssetInfo.iassetTokenNameAscii,
2640
+ )
2641
+ ).datum.redemptionReimbursementRatio,
2642
+ 11_000_000n,
2643
+ ),
2644
+ ),
2645
+ mkAssetsOf(iassetAc, 11_000_000n),
2646
+ ),
2647
+ );
2648
+ });
2649
+
2650
+ test<MyContext>('multi redemption sell ROBs case (2 ROBs)', async (context: MyContext) => {
2651
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
2652
+
2653
+ const [sysParams, [iusdAssetInfo]] = await init(
2654
+ context.lucid,
2655
+ [iusdInitialAssetCfg()],
2656
+ context.emulator.slot,
2657
+ );
2658
+
2659
+ const initialDeposit = 20_000_000n;
2660
+
2661
+ /**********
2662
+ * user 1 opens ROB
2663
+ ***********/
2664
+ {
2665
+ context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
2666
+
2667
+ await runAndAwaitTx(
2668
+ context.lucid,
2669
+ runOpenCdp(
2670
+ context,
2671
+ sysParams,
2672
+ iusdAssetInfo.iassetTokenNameAscii,
2673
+ adaAssetClass,
2674
+ 100_000_000n,
2675
+ 30_000_000n,
2676
+ ),
2677
+ );
2678
+
2679
+ await runAndAwaitTx(
2680
+ context.lucid,
2681
+ openRob(
2682
+ iusdAssetInfo.iassetTokenNameAscii,
2683
+ initialDeposit,
2684
+ {
2685
+ SellIAssetOrder: {
2686
+ allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
2687
+ },
2688
+ },
2689
+ context.lucid,
2690
+ sysParams,
2691
+ ),
2692
+ );
2693
+ }
2694
+
2695
+ /**********
2696
+ * user 2 opens ROB
2697
+ ***********/
2698
+ {
2699
+ context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);
2700
+
2701
+ await runAndAwaitTx(
2702
+ context.lucid,
2703
+ runOpenCdp(
2704
+ context,
2705
+ sysParams,
2706
+ iusdAssetInfo.iassetTokenNameAscii,
2707
+ adaAssetClass,
2708
+ 100_000_000n,
2709
+ 30_000_000n,
2710
+ ),
2711
+ );
2712
+
2713
+ await runAndAwaitTx(
2714
+ context.lucid,
2715
+ openRob(
2716
+ iusdAssetInfo.iassetTokenNameAscii,
2717
+ initialDeposit,
2718
+ {
2719
+ SellIAssetOrder: {
2720
+ allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
2721
+ },
2722
+ },
2723
+ context.lucid,
2724
+ sysParams,
2725
+ ),
2726
+ );
2727
+ }
2728
+
2729
+ const robUtxo1 = await findSingleRob(
2730
+ context,
2731
+ sysParams,
2732
+ iusdAssetInfo.iassetTokenNameAscii,
2733
+ paymentCredentialOf(context.users.user1.address),
2734
+ );
2735
+ const robUtxo2 = await findSingleRob(
2736
+ context,
2737
+ sysParams,
2738
+ iusdAssetInfo.iassetTokenNameAscii,
2739
+ paymentCredentialOf(context.users.user2.address),
2740
+ );
2741
+
2742
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
2743
+
2744
+ await runAndAwaitTx(
2745
+ context.lucid,
2746
+ runRedeemRob(
2747
+ context,
2748
+ sysParams,
2749
+ [
2750
+ [robUtxo1, 10_000_000n],
2751
+ [robUtxo2, 11_000_000n],
2752
+ ],
2753
+ iusdAssetInfo.iassetTokenNameAscii,
2754
+ adaAssetClass,
2755
+ context.emulator.slot,
2756
+ ),
2757
+ );
2758
+
2759
+ const resultRobUtxo1 = await findSingleRob(
2760
+ context,
2761
+ sysParams,
2762
+ iusdAssetInfo.iassetTokenNameAscii,
2763
+ paymentCredentialOf(context.users.user1.address),
2764
+ );
2765
+ const resultRobUtxo2 = await findSingleRob(
2766
+ context,
2767
+ sysParams,
2768
+ iusdAssetInfo.iassetTokenNameAscii,
2769
+ paymentCredentialOf(context.users.user2.address),
2770
+ );
2771
+
2772
+ const iassetAc: AssetClass = {
2773
+ currencySymbol: fromHex(
2774
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
2775
+ ),
2776
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
2777
+ };
2778
+
2779
+ expectValue(resultRobUtxo1.utxo.assets, 'Wrong ROB1 value').toEqual(
2780
+ addAssets(
2781
+ mkLovelacesOf(MIN_ROB_COLLATERAL_AMT + 10_000_000n),
2782
+ mkAssetsOf(
2783
+ iassetAc,
2784
+ initialDeposit -
2785
+ 10_000_000n +
2786
+ calculateFeeFromRatio(
2787
+ (
2788
+ await findIAssetNew(
2789
+ context,
2790
+ sysParams,
2791
+ iusdAssetInfo.iassetTokenNameAscii,
2792
+ )
2793
+ ).datum.redemptionReimbursementRatio,
2794
+ 10_000_000n,
2795
+ ),
2796
+ ),
2797
+ ),
2798
+ );
2799
+
2800
+ expectValue(resultRobUtxo2.utxo.assets, 'Wrong ROB2 value').toEqual(
2801
+ addAssets(
2802
+ mkLovelacesOf(MIN_ROB_COLLATERAL_AMT + 11_000_000n),
2803
+ mkAssetsOf(
2804
+ iassetAc,
2805
+ initialDeposit -
2806
+ 11_000_000n +
2807
+ calculateFeeFromRatio(
2808
+ (
2809
+ await findIAssetNew(
2810
+ context,
2811
+ sysParams,
2812
+ iusdAssetInfo.iassetTokenNameAscii,
2813
+ )
2814
+ ).datum.redemptionReimbursementRatio,
2815
+ 11_000_000n,
2816
+ ),
2817
+ ),
2818
+ ),
2819
+ );
2820
+ });
2821
+
2822
+ test<MyContext>('multi redemption BUY ROBs case (20 ROBs)', async (context: MyContext) => {
2823
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
2824
+
2825
+ const [sysParams, [iusdAssetInfo]] = await init(
2826
+ context.lucid,
2827
+ [iusdInitialAssetCfg()],
2828
+ context.emulator.slot,
2829
+ );
2830
+
2831
+ const initialDeposit = 20_000_000n;
2832
+
2833
+ context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
2834
+
2835
+ await repeat(19, async () => {
2836
+ // Adjsuted for Pyth updates
2837
+ await runAndAwaitTx(
2838
+ context.lucid,
2839
+ openRob(
2840
+ iusdAssetInfo.iassetTokenNameAscii,
2841
+ initialDeposit,
2842
+ {
2843
+ BuyIAssetOrder: {
2844
+ collateralAsset: adaAssetClass,
2845
+ maxPrice: rationalFromInt(1n),
2846
+ },
2847
+ },
2848
+ context.lucid,
2849
+ sysParams,
2850
+ ),
2851
+ );
2852
+ });
2853
+
2854
+ const allRobs = await findAllRobs(
2855
+ context.lucid,
2856
+ sysParams,
2857
+ iusdAssetInfo.iassetTokenNameAscii,
2858
+ );
2859
+
2860
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
2861
+
2862
+ await runAndAwaitTx(
2863
+ context.lucid,
2864
+ runOpenCdp(
2865
+ context,
2866
+ sysParams,
2867
+ iusdAssetInfo.iassetTokenNameAscii,
2868
+ adaAssetClass,
2869
+ 100_000_000n,
2870
+ 30_000_000n,
2871
+ ),
2872
+ );
2873
+
2874
+ await benchmarkAndAwaitTx(
2875
+ 'ROB - redeem 20 buy orders',
2876
+ await runRedeemRob(
2877
+ context,
2878
+ sysParams,
2879
+ allRobs.map((rob) => [rob, 1_000_000n]),
2880
+ iusdAssetInfo.iassetTokenNameAscii,
2881
+ adaAssetClass,
2882
+ context.emulator.slot,
2883
+ ),
2884
+ context.lucid,
2885
+ context.emulator,
2886
+ );
2887
+ });
2888
+
2889
+ test<MyContext>('multi redemption SELL ROBs case single allowed collateral (18 ROBs)', async (context: MyContext) => {
2890
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
2891
+
2892
+ const [sysParams, [iusdAssetInfo]] = await init(
2893
+ context.lucid,
2894
+ [iusdInitialAssetCfg()],
2895
+ context.emulator.slot,
2896
+ );
2897
+
2898
+ const initialDeposit = 5_000_000n;
2899
+
2900
+ context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
2901
+ await runAndAwaitTx(
2902
+ context.lucid,
2903
+ runOpenCdp(
2904
+ context,
2905
+ sysParams,
2906
+ iusdAssetInfo.iassetTokenNameAscii,
2907
+ adaAssetClass,
2908
+ 1_000_000_000n,
2909
+ initialDeposit * 18n +
2910
+ // Should cover the minting fee
2911
+ 1_000_000n,
2912
+ ),
2913
+ );
2914
+
2915
+ await repeat(18, async () => {
2916
+ await runAndAwaitTx(
2917
+ context.lucid,
2918
+ openRob(
2919
+ iusdAssetInfo.iassetTokenNameAscii,
2920
+ initialDeposit,
2921
+ {
2922
+ SellIAssetOrder: {
2923
+ allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
2924
+ },
2925
+ },
2926
+ context.lucid,
2927
+ sysParams,
2928
+ ),
2929
+ );
2930
+ });
2931
+
2932
+ const allRobs = await findAllRobs(
2933
+ context.lucid,
2934
+ sysParams,
2935
+ iusdAssetInfo.iassetTokenNameAscii,
2936
+ );
2937
+
2938
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
2939
+
2940
+ await benchmarkAndAwaitTx(
2941
+ 'ROB - redeem 18 SELL orders, single allowed collateral',
2942
+ await runRedeemRob(
2943
+ context,
2944
+ sysParams,
2945
+ allRobs.map((rob) => [rob, 1_000_000n]),
2946
+ iusdAssetInfo.iassetTokenNameAscii,
2947
+ adaAssetClass,
2948
+ context.emulator.slot,
2949
+ ),
2950
+ context.lucid,
2951
+ context.emulator,
2952
+ );
2953
+ });
2954
+
2955
+ test<MyContext>('Redeem buy order with expired oracle fails', async (context: MyContext) => {
2956
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
2957
+
2958
+ const [sysParams, [iusdAssetInfo]] = await init(
2959
+ context.lucid,
2960
+ [iusdInitialAssetCfg()],
2961
+ context.emulator.slot,
2962
+ );
2963
+
2964
+ const [ownPkh, _] = await addrDetails(context.lucid);
2965
+
2966
+ await runAndAwaitTx(
2967
+ context.lucid,
2968
+ runOpenCdp(
2969
+ context,
2970
+ sysParams,
2971
+ iusdAssetInfo.iassetTokenNameAscii,
2972
+ adaAssetClass,
2973
+ 100_000_000n,
2974
+ 30_000_000n,
2975
+ ),
2976
+ );
2977
+
2978
+ await runAndAwaitTx(
2979
+ context.lucid,
2980
+ openRob(
2981
+ iusdAssetInfo.iassetTokenNameAscii,
2982
+ 20_000_000n,
2983
+ {
2984
+ BuyIAssetOrder: {
2985
+ collateralAsset: adaAssetClass,
2986
+ maxPrice: rationalFromInt(1n),
2987
+ },
2988
+ },
2989
+ context.lucid,
2990
+ sysParams,
2991
+ ),
2992
+ );
2993
+
2994
+ const robUtxo = await findSingleRob(
2995
+ context,
2996
+ sysParams,
2997
+ iusdAssetInfo.iassetTokenNameAscii,
2998
+ ownPkh,
2999
+ );
3000
+
3001
+ const iassetAc: AssetClass = {
3002
+ currencySymbol: fromHex(
3003
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
3004
+ ),
3005
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
3006
+ };
3007
+
3008
+ expect(
3009
+ assetClassValueOf(robUtxo.utxo.assets, iassetAc),
3010
+ 'ROB should have no iassets before redemption',
3011
+ ).toBe(0n);
3012
+
3013
+ const redemptionIAssetAmt = 11_000_000n;
3014
+
3015
+ //Await until oracle is expired
3016
+ await waitForOracleExpiration(
3017
+ context,
3018
+ sysParams,
3019
+ iusdAssetInfo.iassetTokenNameAscii,
3020
+ adaAssetClass,
3021
+ );
3022
+
3023
+ await expectScriptFailure(
3024
+ 'X',
3025
+ testRedeemRob(
3026
+ [[robUtxo.utxo, redemptionIAssetAmt]],
3027
+ await findPriceOracleFromCollateralAsset(
3028
+ context.lucid,
3029
+ await findCollateralAssetNew(
3030
+ context,
3031
+ sysParams,
3032
+ iusdAssetInfo.iassetTokenNameAscii,
3033
+ adaAssetClass,
3034
+ ),
3035
+ ),
3036
+ (
3037
+ await findIAssetNew(
3038
+ context,
3039
+ sysParams,
3040
+ iusdAssetInfo.iassetTokenNameAscii,
3041
+ )
3042
+ ).utxo,
3043
+ (
3044
+ await findCollateralAssetNew(
3045
+ context,
3046
+ sysParams,
3047
+ iusdAssetInfo.iassetTokenNameAscii,
3048
+ adaAssetClass,
3049
+ )
3050
+ ).utxo,
3051
+ context.lucid,
3052
+ context.emulator.slot,
3053
+ sysParams,
3054
+ ),
3055
+ );
3056
+ });
3057
+
3058
+ test<MyContext>('Redeem sell order with expired oracle fails', async (context: MyContext) => {
3059
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
3060
+
3061
+ const [sysParams, [iusdAssetInfo]] = await init(
3062
+ context.lucid,
3063
+ [iusdInitialAssetCfg()],
3064
+ context.emulator.slot,
3065
+ );
3066
+
3067
+ const [ownPkh, _] = await addrDetails(context.lucid);
3068
+
3069
+ await runAndAwaitTx(
3070
+ context.lucid,
3071
+ runOpenCdp(
3072
+ context,
3073
+ sysParams,
3074
+ iusdAssetInfo.iassetTokenNameAscii,
3075
+ adaAssetClass,
3076
+ 100_000_000n,
3077
+ 30_000_000n,
3078
+ ),
3079
+ );
3080
+
3081
+ await runAndAwaitTx(
3082
+ context.lucid,
3083
+ openRob(
3084
+ iusdAssetInfo.iassetTokenNameAscii,
3085
+ 20_000_000n,
3086
+ {
3087
+ SellIAssetOrder: {
3088
+ allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
3089
+ },
3090
+ },
3091
+ context.lucid,
3092
+ sysParams,
3093
+ ),
3094
+ );
3095
+
3096
+ const robUtxo = await findSingleRob(
3097
+ context,
3098
+ sysParams,
3099
+ iusdAssetInfo.iassetTokenNameAscii,
3100
+ ownPkh,
3101
+ );
3102
+
3103
+ const iassetAc: AssetClass = {
3104
+ currencySymbol: fromHex(
3105
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
3106
+ ),
3107
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
3108
+ };
3109
+
3110
+ expect(
3111
+ assetClassValueOf(robUtxo.utxo.assets, iassetAc),
3112
+ 'ROB should have no iassets before redemption',
3113
+ ).toBe(20_000_000n);
3114
+
3115
+ const redemptionIAssetAmt = 11_000_000n;
3116
+
3117
+ //Await until oracle is expired
3118
+ await waitForOracleExpiration(
3119
+ context,
3120
+ sysParams,
3121
+ iusdAssetInfo.iassetTokenNameAscii,
3122
+ adaAssetClass,
3123
+ );
3124
+
3125
+ await expectScriptFailure(
3126
+ 'X',
3127
+ testRedeemRob(
3128
+ [[robUtxo.utxo, redemptionIAssetAmt]],
3129
+ await findPriceOracleFromCollateralAsset(
3130
+ context.lucid,
3131
+ await findCollateralAssetNew(
3132
+ context,
3133
+ sysParams,
3134
+ iusdAssetInfo.iassetTokenNameAscii,
3135
+ adaAssetClass,
3136
+ ),
3137
+ ),
3138
+ (
3139
+ await findIAssetNew(
3140
+ context,
3141
+ sysParams,
3142
+ iusdAssetInfo.iassetTokenNameAscii,
3143
+ )
3144
+ ).utxo,
3145
+ (
3146
+ await findCollateralAssetNew(
3147
+ context,
3148
+ sysParams,
3149
+ iusdAssetInfo.iassetTokenNameAscii,
3150
+ adaAssetClass,
3151
+ )
3152
+ ).utxo,
3153
+ context.lucid,
3154
+ context.emulator.slot,
3155
+ sysParams,
3156
+ ),
3157
+ );
3158
+ });
3159
+
3160
+ test<IndigoTestContext>('Redeem with delisted iAsset fails', async (context: IndigoTestContext) => {
3161
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
3162
+
3163
+ const [sysParams, [iusdAssetInfo]] = await init(
3164
+ context.lucid,
3165
+ [iusdInitialAssetCfg()],
3166
+ context.emulator.slot,
3167
+ );
3168
+
3169
+ const [ownPkh, _] = await addrDetails(context.lucid);
3170
+
3171
+ await runAndAwaitTx(
3172
+ context.lucid,
3173
+ runOpenCdp(
3174
+ context,
3175
+ sysParams,
3176
+ iusdAssetInfo.iassetTokenNameAscii,
3177
+ adaAssetClass,
3178
+ 100_000_000n,
3179
+ 30_000_000n,
3180
+ ),
3181
+ );
3182
+
3183
+ await runAndAwaitTx(
3184
+ context.lucid,
3185
+ openRob(
3186
+ iusdAssetInfo.iassetTokenNameAscii,
3187
+ 20_000_000n,
3188
+ {
3189
+ BuyIAssetOrder: {
3190
+ collateralAsset: adaAssetClass,
3191
+ maxPrice: rationalFromInt(1n),
3192
+ },
3193
+ },
3194
+ context.lucid,
3195
+ sysParams,
3196
+ ),
3197
+ );
3198
+
3199
+ const robUtxo = await findSingleRob(
3200
+ context,
3201
+ sysParams,
3202
+ iusdAssetInfo.iassetTokenNameAscii,
3203
+ ownPkh,
3204
+ );
3205
+
3206
+ const iassetAc: AssetClass = {
3207
+ currencySymbol: fromHex(
3208
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
3209
+ ),
3210
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
3211
+ };
3212
+
3213
+ expect(
3214
+ assetClassValueOf(robUtxo.utxo.assets, iassetAc),
3215
+ 'ROB should have no iassets before redemption',
3216
+ ).toBe(0n);
3217
+
3218
+ const redemptionIAssetAmt = 11_000_000n;
3219
+
3220
+ const govUtxo = await findGov(
3221
+ context.lucid,
3222
+ sysParams.validatorHashes.govHash,
3223
+ fromSystemParamsAsset(sysParams.govParams.govNFT),
3224
+ );
3225
+
3226
+ const collateralAssetToModify = await findCollateralAsset(
3227
+ context.lucid,
3228
+ sysParams,
3229
+ fromSystemParamsAsset(sysParams.cdpCreatorParams.collateralAssetAuthTk),
3230
+ iusdAssetInfo.iassetTokenNameAscii,
3231
+ adaAssetClass,
3232
+ );
3233
+
3234
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
3235
+
3236
+ const [tx, pollId] = await createProposal(
3237
+ {
3238
+ UpdateCollateralAsset: {
3239
+ correspondingIAsset: fromHex(fromText('iUSD')),
3240
+ collateralAsset: adaAssetClass,
3241
+ newAssetExtraDecimals: 0n,
3242
+ newAssetPriceInfo: {
3243
+ Delisted: { price: rationalFromInt(1n) },
3244
+ },
3245
+ newInterestOracleNft: collateralAssetToModify.datum.interestOracleNft,
3246
+ newLiquidationRatio: rationalFromInt(1n),
3247
+ newMaintenanceRatio: rationalFromInt(1n),
3248
+ newRedemptionRatio: rationalFromInt(100n),
3249
+ newMinCollateralAmt: collateralAssetToModify.datum.minCollateralAmt,
3250
+ },
3251
+ },
3252
+ null,
3253
+ sysParams,
3254
+ context.lucid,
3255
+ context.emulator.slot,
3256
+ govUtxo.utxo,
3257
+ [],
3258
+ );
3259
+
3260
+ await runAndAwaitTxBuilder(context.lucid, tx);
3261
+
3262
+ await processSuccessfulProposal(
3263
+ pollId,
3264
+ null,
3265
+ null,
3266
+ null,
3267
+ null,
3268
+ collateralAssetToModify.utxo,
3269
+ null,
3270
+ sysParams,
3271
+ context,
3272
+ );
3273
+
3274
+ {
3275
+ const orefs = await findAllNecessaryOrefs(
3276
+ context.lucid,
3277
+ sysParams,
3278
+ iusdAssetInfo.iassetTokenNameAscii,
3279
+ adaAssetClass,
3280
+ );
3281
+
3282
+ await expectScriptFailure(
3283
+ 'W',
3284
+ testRedeemRob(
3285
+ [[robUtxo.utxo, redemptionIAssetAmt]],
3286
+ undefined,
3287
+ orefs.iasset.utxo,
3288
+ orefs.collateralAsset.utxo,
3289
+ context.lucid,
3290
+ context.emulator.slot,
3291
+ sysParams,
3292
+ ),
3293
+ );
3294
+ }
3295
+ });
3296
+
3297
+ test<MyContext>('BUY order redemption, collateral with more decimal digits', async (context: MyContext) => {
3298
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
3299
+
3300
+ const extraDecimals = 2n;
3301
+
3302
+ const [sysParams, [iusdAssetInfo]] = await init(
3303
+ context.lucid,
3304
+ [
3305
+ {
3306
+ ...iusdInitialAssetCfg(),
3307
+ collateralAssets: [
3308
+ mkBaseCollateralAsset(
3309
+ collateralAssetA,
3310
+ 0n,
3311
+ rationalFromInt(1n),
3312
+ extraDecimals,
3313
+ ),
3314
+ ],
3315
+ },
3316
+ ],
3317
+ context.emulator.slot,
3318
+ );
3319
+
3320
+ const [ownPkh, _] = await addrDetails(context.lucid);
3321
+
3322
+ await runAndAwaitTx(
3323
+ context.lucid,
3324
+ runOpenCdp(
3325
+ context,
3326
+ sysParams,
3327
+ iusdAssetInfo.iassetTokenNameAscii,
3328
+ collateralAssetA,
3329
+ 10_000_000_000n,
3330
+ 30_000_000n,
3331
+ ),
3332
+ );
3333
+
3334
+ const robOrderAmount = 2_000_000_000n;
3335
+
3336
+ await runAndAwaitTx(
3337
+ context.lucid,
3338
+ openRob(
3339
+ iusdAssetInfo.iassetTokenNameAscii,
3340
+ robOrderAmount,
3341
+ {
3342
+ BuyIAssetOrder: {
3343
+ collateralAsset: collateralAssetA,
3344
+ maxPrice: rationalFromInt(100n),
3345
+ },
3346
+ },
3347
+ context.lucid,
3348
+ sysParams,
3349
+ ),
3350
+ );
3351
+
3352
+ const robUtxo = await findSingleRob(
3353
+ context,
3354
+ sysParams,
3355
+ iusdAssetInfo.iassetTokenNameAscii,
3356
+ ownPkh,
3357
+ );
3358
+
3359
+ const iassetAc: AssetClass = {
3360
+ currencySymbol: fromHex(
3361
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
3362
+ ),
3363
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
3364
+ };
3365
+
3366
+ expect(
3367
+ assetClassValueOf(robUtxo.utxo.assets, iassetAc),
3368
+ 'ROB should have no iassets before redemption',
3369
+ ).toBe(0n);
3370
+
3371
+ const payoutIAssetAmt = 11_000_000n;
3372
+
3373
+ await runAndAwaitTx(
3374
+ context.lucid,
3375
+ runRedeemRob(
3376
+ context,
3377
+ sysParams,
3378
+ [[robUtxo, payoutIAssetAmt]],
3379
+ iusdAssetInfo.iassetTokenNameAscii,
3380
+ collateralAssetA,
3381
+ context.emulator.slot,
3382
+ ),
3383
+ );
3384
+
3385
+ const redeemedRob = await findSingleRob(
3386
+ context,
3387
+ sysParams,
3388
+ iusdAssetInfo.iassetTokenNameAscii,
3389
+ ownPkh,
3390
+ );
3391
+
3392
+ expect(
3393
+ assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
3394
+ 'ROB has wrong number of iassets after redemption',
3395
+ ).toBe(payoutIAssetAmt);
3396
+
3397
+ expect(
3398
+ assetClassValueOf(redeemedRob.utxo.assets, collateralAssetA),
3399
+ 'ROB has wrong number of collateral asset after redemption',
3400
+ ).toBe(
3401
+ robOrderAmount -
3402
+ (payoutIAssetAmt -
3403
+ rationalFloor(
3404
+ rationalMul(
3405
+ rationalFromInt(payoutIAssetAmt),
3406
+ iusdInitialAssetCfg().redemptionReimbursementRatio,
3407
+ ),
3408
+ )) *
3409
+ 10n ** extraDecimals,
3410
+ );
3411
+ });
3412
+
3413
+ test<MyContext>('BUY order redemption, collateral with less decimal digits', async (context: MyContext) => {
3414
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
3415
+
3416
+ const extraDecimals = -2n;
3417
+
3418
+ const [sysParams, [iusdAssetInfo]] = await init(
3419
+ context.lucid,
3420
+ [
3421
+ {
3422
+ ...iusdInitialAssetCfg(),
3423
+ collateralAssets: [
3424
+ mkBaseCollateralAsset(
3425
+ collateralAssetA,
3426
+ 0n,
3427
+ rationalFromInt(1n),
3428
+ extraDecimals,
3429
+ ),
3430
+ ],
3431
+ },
3432
+ ],
3433
+ context.emulator.slot,
3434
+ );
3435
+
3436
+ const [ownPkh, _] = await addrDetails(context.lucid);
3437
+
3438
+ await runAndAwaitTx(
3439
+ context.lucid,
3440
+ runOpenCdp(
3441
+ context,
3442
+ sysParams,
3443
+ iusdAssetInfo.iassetTokenNameAscii,
3444
+ collateralAssetA,
3445
+ 10_000_000n,
3446
+ 300_000_000n,
3447
+ ),
3448
+ );
3449
+
3450
+ const robOrderAmount = 200_000n;
3451
+
3452
+ await runAndAwaitTx(
3453
+ context.lucid,
3454
+ openRob(
3455
+ iusdAssetInfo.iassetTokenNameAscii,
3456
+ robOrderAmount,
3457
+ {
3458
+ BuyIAssetOrder: {
3459
+ collateralAsset: collateralAssetA,
3460
+ maxPrice: { numerator: 1n, denominator: 100n },
3461
+ },
3462
+ },
3463
+ context.lucid,
3464
+ sysParams,
3465
+ ),
3466
+ );
3467
+
3468
+ const robUtxo = await findSingleRob(
3469
+ context,
3470
+ sysParams,
3471
+ iusdAssetInfo.iassetTokenNameAscii,
3472
+ ownPkh,
3473
+ );
3474
+
3475
+ const iassetAc: AssetClass = {
3476
+ currencySymbol: fromHex(
3477
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
3478
+ ),
3479
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
3480
+ };
3481
+
3482
+ expect(
3483
+ assetClassValueOf(robUtxo.utxo.assets, iassetAc),
3484
+ 'ROB should have no iassets before redemption',
3485
+ ).toBe(0n);
3486
+
3487
+ const payoutIAssetAmt = 11_000_000n;
3488
+
3489
+ await runAndAwaitTx(
3490
+ context.lucid,
3491
+ runRedeemRob(
3492
+ context,
3493
+ sysParams,
3494
+ [[robUtxo, payoutIAssetAmt]],
3495
+ iusdAssetInfo.iassetTokenNameAscii,
3496
+ collateralAssetA,
3497
+ context.emulator.slot,
3498
+ ),
3499
+ );
3500
+
3501
+ const redeemedRob = await findSingleRob(
3502
+ context,
3503
+ sysParams,
3504
+ iusdAssetInfo.iassetTokenNameAscii,
3505
+ ownPkh,
3506
+ );
3507
+
3508
+ expect(
3509
+ assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
3510
+ 'ROB has wrong number of iassets after redemption',
3511
+ ).toBe(payoutIAssetAmt);
3512
+
3513
+ expect(
3514
+ assetClassValueOf(redeemedRob.utxo.assets, collateralAssetA),
3515
+ 'ROB has wrong number of collateral asset after redemption',
3516
+ ).toBe(
3517
+ robOrderAmount -
3518
+ (payoutIAssetAmt -
3519
+ rationalFloor(
3520
+ rationalMul(
3521
+ rationalFromInt(payoutIAssetAmt),
3522
+ iusdInitialAssetCfg().redemptionReimbursementRatio,
3523
+ ),
3524
+ )) /
3525
+ 10n ** -extraDecimals,
3526
+ );
3527
+ });
3528
+
3529
+ test<MyContext>('SELL order redemption, collateral with more decimal digits', async (context: MyContext) => {
3530
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
3531
+
3532
+ const extraDecimals = 2n;
3533
+
3534
+ const [sysParams, [iusdAssetInfo]] = await init(
3535
+ context.lucid,
3536
+ [
3537
+ {
3538
+ ...iusdInitialAssetCfg(),
3539
+ collateralAssets: [
3540
+ mkBaseCollateralAsset(
3541
+ collateralAssetA,
3542
+ 0n,
3543
+ rationalFromInt(1n),
3544
+ extraDecimals,
3545
+ ),
3546
+ ],
3547
+ },
3548
+ ],
3549
+ context.emulator.slot,
3550
+ );
3551
+
3552
+ const [ownPkh, _] = await addrDetails(context.lucid);
3553
+
3554
+ await runAndAwaitTx(
3555
+ context.lucid,
3556
+ runOpenCdp(
3557
+ context,
3558
+ sysParams,
3559
+ iusdAssetInfo.iassetTokenNameAscii,
3560
+ collateralAssetA,
3561
+ 10_000_000_000n,
3562
+ 30_000_000n,
3563
+ ),
3564
+ );
3565
+
3566
+ const robOrderAmount = 20_000_000n;
3567
+
3568
+ await runAndAwaitTx(
3569
+ context.lucid,
3570
+ openRob(
3571
+ iusdAssetInfo.iassetTokenNameAscii,
3572
+ robOrderAmount,
3573
+ {
3574
+ SellIAssetOrder: {
3575
+ allowedCollateralAssets: [
3576
+ [collateralAssetA, rationalFromInt(100n)],
3577
+ ],
3578
+ },
3579
+ },
3580
+ context.lucid,
3581
+ sysParams,
3582
+ ),
3583
+ );
3584
+
3585
+ const robUtxo = await findSingleRob(
3586
+ context,
3587
+ sysParams,
3588
+ iusdAssetInfo.iassetTokenNameAscii,
3589
+ ownPkh,
3590
+ );
3591
+
3592
+ const iassetAc: AssetClass = {
3593
+ currencySymbol: fromHex(
3594
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
3595
+ ),
3596
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
3597
+ };
3598
+
3599
+ expect(
3600
+ assetClassValueOf(robUtxo.utxo.assets, collateralAssetA),
3601
+ 'ROB should have no collateral assets before redemption',
3602
+ ).toBe(0n);
3603
+
3604
+ const payoutCollateralAmt = 1_100_000_000n;
3605
+
3606
+ await runAndAwaitTx(
3607
+ context.lucid,
3608
+ runRedeemRob(
3609
+ context,
3610
+ sysParams,
3611
+ [[robUtxo, payoutCollateralAmt]],
3612
+ iusdAssetInfo.iassetTokenNameAscii,
3613
+ collateralAssetA,
3614
+ context.emulator.slot,
3615
+ ),
3616
+ );
3617
+
3618
+ const redeemedRob = await findSingleRob(
3619
+ context,
3620
+ sysParams,
3621
+ iusdAssetInfo.iassetTokenNameAscii,
3622
+ ownPkh,
3623
+ );
3624
+
3625
+ expect(
3626
+ assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
3627
+ 'ROB has wrong number of iassets after redemption',
3628
+ ).toBe(
3629
+ robOrderAmount -
3630
+ (payoutCollateralAmt -
3631
+ rationalFloor(
3632
+ rationalMul(
3633
+ rationalFromInt(payoutCollateralAmt),
3634
+ iusdInitialAssetCfg().redemptionReimbursementRatio,
3635
+ ),
3636
+ )) /
3637
+ 10n ** extraDecimals,
3638
+ );
3639
+
3640
+ expect(
3641
+ assetClassValueOf(redeemedRob.utxo.assets, collateralAssetA),
3642
+ 'ROB has wrong number of collateral asset after redemption',
3643
+ ).toBe(payoutCollateralAmt);
3644
+ });
3645
+
3646
+ test<MyContext>('SELL order redemption, collateral with less decimal digits', async (context: MyContext) => {
3647
+ context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
3648
+
3649
+ const extraDecimals = -2n;
3650
+
3651
+ const [sysParams, [iusdAssetInfo]] = await init(
3652
+ context.lucid,
3653
+ [
3654
+ {
3655
+ ...iusdInitialAssetCfg(),
3656
+ collateralAssets: [
3657
+ mkBaseCollateralAsset(
3658
+ collateralAssetA,
3659
+ 0n,
3660
+ rationalFromInt(1n),
3661
+ extraDecimals,
3662
+ ),
3663
+ ],
3664
+ },
3665
+ ],
3666
+ context.emulator.slot,
3667
+ );
3668
+
3669
+ const [ownPkh, _] = await addrDetails(context.lucid);
3670
+
3671
+ await runAndAwaitTx(
3672
+ context.lucid,
3673
+ runOpenCdp(
3674
+ context,
3675
+ sysParams,
3676
+ iusdAssetInfo.iassetTokenNameAscii,
3677
+ collateralAssetA,
3678
+ 10_000_000n,
3679
+ 300_000_000n,
3680
+ ),
3681
+ );
3682
+
3683
+ const robOrderAmount = 20_000_000n;
3684
+
3685
+ await runAndAwaitTx(
3686
+ context.lucid,
3687
+ openRob(
3688
+ iusdAssetInfo.iassetTokenNameAscii,
3689
+ robOrderAmount,
3690
+ {
3691
+ SellIAssetOrder: {
3692
+ allowedCollateralAssets: [
3693
+ [collateralAssetA, { numerator: 1n, denominator: 100n }],
3694
+ ],
3695
+ },
3696
+ },
3697
+ context.lucid,
3698
+ sysParams,
3699
+ ),
3700
+ );
3701
+
3702
+ const robUtxo = await findSingleRob(
3703
+ context,
3704
+ sysParams,
3705
+ iusdAssetInfo.iassetTokenNameAscii,
3706
+ ownPkh,
3707
+ );
3708
+
3709
+ const iassetAc: AssetClass = {
3710
+ currencySymbol: fromHex(
3711
+ sysParams.robParams.iassetPolicyId.unCurrencySymbol,
3712
+ ),
3713
+ tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
3714
+ };
3715
+
3716
+ expect(
3717
+ assetClassValueOf(robUtxo.utxo.assets, collateralAssetA),
3718
+ 'ROB should have no collateral assets before redemption',
3719
+ ).toBe(0n);
3720
+
3721
+ const payoutCollateralAmt = 110_000n;
3722
+
3723
+ await runAndAwaitTx(
3724
+ context.lucid,
3725
+ runRedeemRob(
3726
+ context,
3727
+ sysParams,
3728
+ [[robUtxo, payoutCollateralAmt]],
3729
+ iusdAssetInfo.iassetTokenNameAscii,
3730
+ collateralAssetA,
3731
+ context.emulator.slot,
3732
+ ),
3733
+ );
3734
+
3735
+ const redeemedRob = await findSingleRob(
3736
+ context,
3737
+ sysParams,
3738
+ iusdAssetInfo.iassetTokenNameAscii,
3739
+ ownPkh,
3740
+ );
3741
+
3742
+ expect(
3743
+ assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
3744
+ 'ROB has wrong number of iassets after redemption',
3745
+ ).toBe(
3746
+ robOrderAmount -
3747
+ (payoutCollateralAmt -
3748
+ rationalFloor(
3749
+ rationalMul(
3750
+ rationalFromInt(payoutCollateralAmt),
3751
+ iusdInitialAssetCfg().redemptionReimbursementRatio,
3752
+ ),
3753
+ )) *
3754
+ 10n ** -extraDecimals,
3755
+ );
3756
+
3757
+ expect(
3758
+ assetClassValueOf(redeemedRob.utxo.assets, collateralAssetA),
3759
+ 'ROB has wrong number of collateral asset after redemption',
3760
+ ).toBe(payoutCollateralAmt);
3761
+ });
3762
+ });