@drift-labs/sdk 2.145.0 → 2.146.0-alpha.13

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 (99) hide show
  1. package/.env +4 -0
  2. package/VERSION +1 -1
  3. package/lib/browser/accounts/grpcMultiUserAccountSubscriber.js +8 -1
  4. package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.d.ts +99 -7
  5. package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.js +435 -144
  6. package/lib/browser/adminClient.d.ts +5 -1
  7. package/lib/browser/adminClient.js +57 -23
  8. package/lib/browser/constants/numericConstants.d.ts +2 -0
  9. package/lib/browser/constants/numericConstants.js +5 -1
  10. package/lib/browser/constants/perpMarkets.js +0 -2
  11. package/lib/browser/decode/user.js +4 -0
  12. package/lib/browser/driftClient.d.ts +25 -10
  13. package/lib/browser/driftClient.js +238 -41
  14. package/lib/browser/driftClientConfig.d.ts +7 -2
  15. package/lib/browser/idl/drift.json +245 -22
  16. package/lib/browser/index.d.ts +4 -0
  17. package/lib/browser/index.js +9 -1
  18. package/lib/browser/marginCalculation.d.ts +86 -0
  19. package/lib/browser/marginCalculation.js +209 -0
  20. package/lib/browser/math/margin.d.ts +1 -1
  21. package/lib/browser/math/margin.js +8 -1
  22. package/lib/browser/math/position.d.ts +1 -0
  23. package/lib/browser/math/position.js +10 -2
  24. package/lib/browser/math/spotPosition.d.ts +1 -1
  25. package/lib/browser/math/spotPosition.js +3 -2
  26. package/lib/browser/math/superStake.d.ts +3 -2
  27. package/lib/browser/types.d.ts +13 -0
  28. package/lib/browser/types.js +12 -1
  29. package/lib/browser/user.d.ts +59 -11
  30. package/lib/browser/user.js +348 -43
  31. package/lib/node/accounts/grpcMultiUserAccountSubscriber.d.ts.map +1 -1
  32. package/lib/node/accounts/grpcMultiUserAccountSubscriber.js +8 -1
  33. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts +99 -7
  34. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts.map +1 -1
  35. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.js +435 -144
  36. package/lib/node/adminClient.d.ts +5 -1
  37. package/lib/node/adminClient.d.ts.map +1 -1
  38. package/lib/node/adminClient.js +57 -23
  39. package/lib/node/constants/numericConstants.d.ts +2 -0
  40. package/lib/node/constants/numericConstants.d.ts.map +1 -1
  41. package/lib/node/constants/numericConstants.js +5 -1
  42. package/lib/node/constants/perpMarkets.d.ts.map +1 -1
  43. package/lib/node/constants/perpMarkets.js +0 -2
  44. package/lib/node/decode/user.d.ts.map +1 -1
  45. package/lib/node/decode/user.js +4 -0
  46. package/lib/node/driftClient.d.ts +25 -10
  47. package/lib/node/driftClient.d.ts.map +1 -1
  48. package/lib/node/driftClient.js +238 -41
  49. package/lib/node/driftClientConfig.d.ts +7 -2
  50. package/lib/node/driftClientConfig.d.ts.map +1 -1
  51. package/lib/node/idl/drift.json +245 -22
  52. package/lib/node/index.d.ts +4 -0
  53. package/lib/node/index.d.ts.map +1 -1
  54. package/lib/node/index.js +9 -1
  55. package/lib/node/marginCalculation.d.ts +87 -0
  56. package/lib/node/marginCalculation.d.ts.map +1 -0
  57. package/lib/node/marginCalculation.js +209 -0
  58. package/lib/node/math/margin.d.ts +1 -1
  59. package/lib/node/math/margin.d.ts.map +1 -1
  60. package/lib/node/math/margin.js +8 -1
  61. package/lib/node/math/position.d.ts +1 -0
  62. package/lib/node/math/position.d.ts.map +1 -1
  63. package/lib/node/math/position.js +10 -2
  64. package/lib/node/math/spotPosition.d.ts +1 -1
  65. package/lib/node/math/spotPosition.d.ts.map +1 -1
  66. package/lib/node/math/spotPosition.js +3 -2
  67. package/lib/node/math/superStake.d.ts +3 -2
  68. package/lib/node/math/superStake.d.ts.map +1 -1
  69. package/lib/node/types.d.ts +13 -0
  70. package/lib/node/types.d.ts.map +1 -1
  71. package/lib/node/types.js +12 -1
  72. package/lib/node/user.d.ts +59 -11
  73. package/lib/node/user.d.ts.map +1 -1
  74. package/lib/node/user.js +348 -43
  75. package/package.json +1 -1
  76. package/scripts/deposit-isolated-positions.ts +110 -0
  77. package/scripts/single-grpc-client-test.ts +71 -21
  78. package/scripts/withdraw-isolated-positions.ts +174 -0
  79. package/src/accounts/grpcMultiUserAccountSubscriber.ts +8 -1
  80. package/src/accounts/webSocketProgramAccountSubscriberV2.ts +566 -167
  81. package/src/adminClient.ts +74 -25
  82. package/src/constants/numericConstants.ts +5 -0
  83. package/src/constants/perpMarkets.ts +0 -3
  84. package/src/decode/user.ts +7 -1
  85. package/src/driftClient.ts +465 -52
  86. package/src/driftClientConfig.ts +15 -8
  87. package/src/idl/drift.json +246 -23
  88. package/src/index.ts +4 -0
  89. package/src/margin/README.md +143 -0
  90. package/src/marginCalculation.ts +306 -0
  91. package/src/math/margin.ts +13 -1
  92. package/src/math/position.ts +12 -2
  93. package/src/math/spotPosition.ts +6 -2
  94. package/src/types.ts +16 -0
  95. package/src/user.ts +623 -81
  96. package/tests/amm/test.ts +1 -1
  97. package/tests/dlob/helpers.ts +6 -3
  98. package/tests/user/getMarginCalculation.ts +405 -0
  99. package/tests/user/test.ts +0 -7
package/tests/amm/test.ts CHANGED
@@ -279,7 +279,7 @@ describe('AMM Tests', () => {
279
279
  longIntensity,
280
280
  shortIntensity,
281
281
  volume24H,
282
- 0
282
+ 0,
283
283
  );
284
284
  const l1 = spreads[0];
285
285
  const s1 = spreads[1];
@@ -44,6 +44,8 @@ export const mockPerpPosition: PerpPosition = {
44
44
  lastBaseAssetAmountPerLp: new BN(0),
45
45
  lastQuoteAssetAmountPerLp: new BN(0),
46
46
  perLpBase: 0,
47
+ positionFlag: 0,
48
+ isolatedPositionScaledBalance: new BN(0),
47
49
  maxMarginRatio: 1,
48
50
  };
49
51
 
@@ -670,11 +672,12 @@ export class MockUserMap implements UserMapInterface {
670
672
  wallet: new Wallet(new Keypair()),
671
673
  programID: PublicKey.default,
672
674
  });
675
+ this.eventEmitter = new EventEmitter();
673
676
  }
674
677
 
675
- public async subscribe(): Promise<void> { }
678
+ public async subscribe(): Promise<void> {}
676
679
 
677
- public async unsubscribe(): Promise<void> { }
680
+ public async unsubscribe(): Promise<void> {}
678
681
 
679
682
  public async addPubkey(userAccountPublicKey: PublicKey): Promise<void> {
680
683
  const user = new User({
@@ -733,7 +736,7 @@ export class MockUserMap implements UserMapInterface {
733
736
  );
734
737
  }
735
738
 
736
- public async updateWithOrderRecord(_record: OrderRecord): Promise<void> { }
739
+ public async updateWithOrderRecord(_record: OrderRecord): Promise<void> {}
737
740
 
738
741
  public values(): IterableIterator<User> {
739
742
  return this.userMap.values();
@@ -0,0 +1,405 @@
1
+ import {
2
+ BN,
3
+ ZERO,
4
+ User,
5
+ UserAccount,
6
+ PublicKey,
7
+ PerpMarketAccount,
8
+ SpotMarketAccount,
9
+ PRICE_PRECISION,
10
+ OraclePriceData,
11
+ BASE_PRECISION,
12
+ QUOTE_PRECISION,
13
+ SPOT_MARKET_BALANCE_PRECISION,
14
+ SpotBalanceType,
15
+ OPEN_ORDER_MARGIN_REQUIREMENT,
16
+ SPOT_MARKET_WEIGHT_PRECISION,
17
+ PositionFlag,
18
+ } from '../../src';
19
+ import { MockUserMap, mockPerpMarkets, mockSpotMarkets } from '../dlob/helpers';
20
+ import { assert } from '../../src/assert/assert';
21
+ import { mockUserAccount as baseMockUserAccount } from './helpers';
22
+ import * as _ from 'lodash';
23
+
24
+ async function makeMockUser(
25
+ myMockPerpMarkets: Array<PerpMarketAccount>,
26
+ myMockSpotMarkets: Array<SpotMarketAccount>,
27
+ myMockUserAccount: UserAccount,
28
+ perpOraclePriceList: number[],
29
+ spotOraclePriceList: number[]
30
+ ): Promise<User> {
31
+ const umap = new MockUserMap();
32
+ const mockUser: User = await umap.mustGet('1');
33
+ mockUser._isSubscribed = true;
34
+ mockUser.driftClient._isSubscribed = true;
35
+ mockUser.driftClient.accountSubscriber.isSubscribed = true;
36
+
37
+ const oraclePriceMap: Record<string, number> = {};
38
+ for (let i = 0; i < myMockPerpMarkets.length; i++) {
39
+ oraclePriceMap[myMockPerpMarkets[i].amm.oracle.toString()] =
40
+ perpOraclePriceList[i] ?? 1;
41
+ }
42
+ for (let i = 0; i < myMockSpotMarkets.length; i++) {
43
+ oraclePriceMap[myMockSpotMarkets[i].oracle.toString()] =
44
+ spotOraclePriceList[i] ?? 1;
45
+ }
46
+
47
+ function getMockUserAccount(): UserAccount {
48
+ return myMockUserAccount;
49
+ }
50
+ function getMockPerpMarket(marketIndex: number): PerpMarketAccount {
51
+ return myMockPerpMarkets[marketIndex];
52
+ }
53
+ function getMockSpotMarket(marketIndex: number): SpotMarketAccount {
54
+ return myMockSpotMarkets[marketIndex];
55
+ }
56
+ function getMockOracle(oracleKey: PublicKey) {
57
+ const data: OraclePriceData = {
58
+ price: new BN(
59
+ (oraclePriceMap[oracleKey.toString()] ?? 1) * PRICE_PRECISION.toNumber()
60
+ ),
61
+ slot: new BN(0),
62
+ confidence: new BN(1),
63
+ hasSufficientNumberOfDataPoints: true,
64
+ };
65
+ return { data, slot: 0 };
66
+ }
67
+ function getOracleDataForPerpMarket(marketIndex: number) {
68
+ const oracle = getMockPerpMarket(marketIndex).amm.oracle;
69
+ return getMockOracle(oracle).data;
70
+ }
71
+ function getOracleDataForSpotMarket(marketIndex: number) {
72
+ const oracle = getMockSpotMarket(marketIndex).oracle;
73
+ return getMockOracle(oracle).data;
74
+ }
75
+
76
+ mockUser.getUserAccount = getMockUserAccount;
77
+ mockUser.driftClient.getPerpMarketAccount = getMockPerpMarket as any;
78
+ mockUser.driftClient.getSpotMarketAccount = getMockSpotMarket as any;
79
+ mockUser.driftClient.getOraclePriceDataAndSlot = getMockOracle as any;
80
+ mockUser.driftClient.getOracleDataForPerpMarket =
81
+ getOracleDataForPerpMarket as any;
82
+ mockUser.driftClient.getOracleDataForSpotMarket =
83
+ getOracleDataForSpotMarket as any;
84
+ return mockUser;
85
+ }
86
+
87
+ describe('getMarginCalculation snapshot', () => {
88
+ it('empty account returns zeroed snapshot', async () => {
89
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
90
+ const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
91
+ const myMockUserAccount = _.cloneDeep(baseMockUserAccount);
92
+
93
+ const user: User = await makeMockUser(
94
+ myMockPerpMarkets,
95
+ myMockSpotMarkets,
96
+ myMockUserAccount,
97
+ [1, 1, 1, 1, 1, 1, 1, 1],
98
+ [1, 1, 1, 1, 1, 1, 1, 1]
99
+ );
100
+
101
+ const calc = user.getMarginCalculation('Initial');
102
+ assert(calc.totalCollateral.eq(ZERO));
103
+ assert(calc.marginRequirement.eq(ZERO));
104
+ assert(calc.numSpotLiabilities === 0);
105
+ assert(calc.numPerpLiabilities === 0);
106
+ });
107
+
108
+ it('quote deposit increases totalCollateral, no requirement', async () => {
109
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
110
+ const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
111
+ const myMockUserAccount = _.cloneDeep(baseMockUserAccount);
112
+
113
+ myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT;
114
+ myMockUserAccount.spotPositions[0].scaledBalance = new BN(
115
+ 10000 * SPOT_MARKET_BALANCE_PRECISION.toNumber()
116
+ );
117
+
118
+ const user: User = await makeMockUser(
119
+ myMockPerpMarkets,
120
+ myMockSpotMarkets,
121
+ myMockUserAccount,
122
+ [1, 1, 1, 1, 1, 1, 1, 1],
123
+ [1, 1, 1, 1, 1, 1, 1, 1]
124
+ );
125
+
126
+ const calc = user.getMarginCalculation('Initial');
127
+ const expected = new BN('10000000000'); // $10k
128
+ assert(calc.totalCollateral.eq(expected));
129
+ assert(calc.marginRequirement.eq(ZERO));
130
+ });
131
+
132
+ it('quote borrow increases requirement and buffer applies', async () => {
133
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
134
+ const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
135
+ const myMockUserAccount = _.cloneDeep(baseMockUserAccount);
136
+
137
+ // Borrow 100 quote
138
+ myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.BORROW;
139
+ myMockUserAccount.spotPositions[0].scaledBalance = new BN(
140
+ 100 * SPOT_MARKET_BALANCE_PRECISION.toNumber()
141
+ );
142
+
143
+ const user: User = await makeMockUser(
144
+ myMockPerpMarkets,
145
+ myMockSpotMarkets,
146
+ myMockUserAccount,
147
+ [1, 1, 1, 1, 1, 1, 1, 1],
148
+ [1, 1, 1, 1, 1, 1, 1, 1]
149
+ );
150
+
151
+ const tenPercent = new BN(1000);
152
+ const calc = user.getMarginCalculation('Initial', {
153
+ liquidationBuffer: tenPercent,
154
+ });
155
+ const liability = new BN(100).mul(QUOTE_PRECISION); // $100
156
+ assert(calc.totalCollateral.eq(ZERO));
157
+ assert(calc.marginRequirement.eq(liability));
158
+ assert(
159
+ calc.marginRequirementPlusBuffer.eq(
160
+ liability.div(new BN(10)).add(calc.marginRequirement) // 10% of liability + margin requirement
161
+ )
162
+ );
163
+ assert(calc.numSpotLiabilities === 1);
164
+ });
165
+
166
+ it('non-quote spot open orders add IM', async () => {
167
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
168
+ const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
169
+ const myMockUserAccount = _.cloneDeep(baseMockUserAccount);
170
+
171
+ // Market 1 (e.g., SOL) with 2 open orders
172
+ myMockUserAccount.spotPositions[1].marketIndex = 1;
173
+ myMockUserAccount.spotPositions[1].openOrders = 2;
174
+
175
+ const user: User = await makeMockUser(
176
+ myMockPerpMarkets,
177
+ myMockSpotMarkets,
178
+ myMockUserAccount,
179
+ [1, 1, 1, 1, 1, 1, 1, 1],
180
+ [1, 1, 1, 1, 1, 1, 1, 1]
181
+ );
182
+
183
+ const calc = user.getMarginCalculation('Initial');
184
+ const expectedIM = new BN(2).mul(OPEN_ORDER_MARGIN_REQUIREMENT);
185
+ assert(calc.marginRequirement.eq(expectedIM));
186
+ });
187
+
188
+ it('perp long liability reflects maintenance requirement', async () => {
189
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
190
+ const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
191
+ const myMockUserAccount = _.cloneDeep(baseMockUserAccount);
192
+
193
+ // 20 base long, -$10 quote (liability)
194
+ myMockUserAccount.perpPositions[0].baseAssetAmount = new BN(20).mul(
195
+ BASE_PRECISION
196
+ );
197
+ myMockUserAccount.perpPositions[0].quoteAssetAmount = new BN(-10).mul(
198
+ QUOTE_PRECISION
199
+ );
200
+
201
+ const user: User = await makeMockUser(
202
+ myMockPerpMarkets,
203
+ myMockSpotMarkets,
204
+ myMockUserAccount,
205
+ [1, 1, 1, 1, 1, 1, 1, 1],
206
+ [1, 1, 1, 1, 1, 1, 1, 1]
207
+ );
208
+
209
+ const calc = user.getMarginCalculation('Maintenance');
210
+ // From existing liquidation test expectations: 2_000_000
211
+ assert(calc.marginRequirement.eq(new BN('2000000')));
212
+ });
213
+
214
+ it.only('maker position reducing: collateral equals maintenance requirement', async () => {
215
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
216
+ const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
217
+ const myMockUserAccount = _.cloneDeep(baseMockUserAccount);
218
+
219
+ myMockUserAccount.perpPositions[0].baseAssetAmount = new BN(200000000).mul(
220
+ BASE_PRECISION
221
+ );
222
+ myMockUserAccount.perpPositions[0].quoteAssetAmount = new BN(
223
+ -180000000
224
+ ).mul(QUOTE_PRECISION);
225
+
226
+ const user: User = await makeMockUser(
227
+ myMockPerpMarkets,
228
+ myMockSpotMarkets,
229
+ myMockUserAccount,
230
+ [1, 1, 1, 1, 1, 1, 1, 1],
231
+ [1, 1, 1, 1, 1, 1, 1, 1]
232
+ );
233
+
234
+ const calc = user.getMarginCalculation('Maintenance');
235
+ console.log('calc.marginRequirement', calc.marginRequirement.toString());
236
+ console.log('calc.totalCollateral', calc.totalCollateral.toString());
237
+ assert(calc.marginRequirement.eq(calc.totalCollateral));
238
+ });
239
+
240
+ it('maker reducing after simulated fill: collateral equals maintenance requirement', async () => {
241
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
242
+ const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
243
+
244
+ // Build maker and taker accounts
245
+ const makerAccount = _.cloneDeep(baseMockUserAccount);
246
+ const takerAccount = _.cloneDeep(baseMockUserAccount);
247
+
248
+ // Oracle price = 1 for perp and spot
249
+ const perpOracles = [1, 1, 1, 1, 1, 1, 1, 1];
250
+ const spotOracles = [1, 1, 1, 1, 1, 1, 1, 1];
251
+
252
+ // Pre-fill: maker has 21 base long at entry 1 ($21 notional), taker flat
253
+ makerAccount.perpPositions[0].baseAssetAmount = new BN(21).mul(
254
+ BASE_PRECISION
255
+ );
256
+ makerAccount.perpPositions[0].quoteEntryAmount = new BN(-21).mul(
257
+ QUOTE_PRECISION
258
+ );
259
+ makerAccount.perpPositions[0].quoteBreakEvenAmount = new BN(-21).mul(
260
+ QUOTE_PRECISION
261
+ );
262
+ // Provide exactly $2 in quote collateral to equal 10% maintenance of 20 notional post-fill
263
+ makerAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT;
264
+ makerAccount.spotPositions[0].scaledBalance = new BN(2).mul(
265
+ SPOT_MARKET_BALANCE_PRECISION
266
+ );
267
+
268
+ // Simulate fill: maker sells 1 base to taker at price = oracle = 1
269
+ // Post-fill maker position: 20 base long with zero unrealized PnL
270
+ const maker: User = await makeMockUser(
271
+ myMockPerpMarkets,
272
+ myMockSpotMarkets,
273
+ makerAccount,
274
+ perpOracles,
275
+ spotOracles
276
+ );
277
+ const taker: User = await makeMockUser(
278
+ myMockPerpMarkets,
279
+ myMockSpotMarkets,
280
+ takerAccount,
281
+ perpOracles,
282
+ spotOracles
283
+ );
284
+
285
+ // Apply synthetic trade deltas to both user accounts
286
+ // Maker: base 21 -> 20; taker: base 0 -> 1. Use quote deltas consistent with price 1, fee 0
287
+ maker.getUserAccount().perpPositions[0].baseAssetAmount = new BN(20).mul(
288
+ BASE_PRECISION
289
+ );
290
+ maker.getUserAccount().perpPositions[0].quoteEntryAmount = new BN(-20).mul(
291
+ QUOTE_PRECISION
292
+ );
293
+ maker.getUserAccount().perpPositions[0].quoteBreakEvenAmount = new BN(
294
+ -20
295
+ ).mul(QUOTE_PRECISION);
296
+ // Align quoteAssetAmount with base value so unrealized PnL = 0 at price 1
297
+ maker.getUserAccount().perpPositions[0].quoteAssetAmount = new BN(-20).mul(
298
+ QUOTE_PRECISION
299
+ );
300
+
301
+ taker.getUserAccount().perpPositions[0].baseAssetAmount = new BN(1).mul(
302
+ BASE_PRECISION
303
+ );
304
+ taker.getUserAccount().perpPositions[0].quoteEntryAmount = new BN(-1).mul(
305
+ QUOTE_PRECISION
306
+ );
307
+ taker.getUserAccount().perpPositions[0].quoteBreakEvenAmount = new BN(
308
+ -1
309
+ ).mul(QUOTE_PRECISION);
310
+ // Also set taker's quoteAssetAmount consistently
311
+ taker.getUserAccount().perpPositions[0].quoteAssetAmount = new BN(-1).mul(
312
+ QUOTE_PRECISION
313
+ );
314
+
315
+ const makerCalc = maker.getMarginCalculation('Maintenance');
316
+ assert(makerCalc.marginRequirement.eq(makerCalc.totalCollateral));
317
+ assert(makerCalc.marginRequirement.gt(ZERO));
318
+ });
319
+
320
+ it('isolated position margin requirement (SDK parity)', async () => {
321
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
322
+ const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
323
+ myMockSpotMarkets[0].oracle = new PublicKey(2);
324
+ myMockSpotMarkets[1].oracle = new PublicKey(5);
325
+ myMockPerpMarkets[0].amm.oracle = new PublicKey(5);
326
+
327
+ // Configure perp market 0 ratios to match on-chain test
328
+ myMockPerpMarkets[0].marginRatioInitial = 1000; // 10%
329
+ myMockPerpMarkets[0].marginRatioMaintenance = 500; // 5%
330
+
331
+ // Configure spot market 1 (e.g., SOL) weights to match on-chain test
332
+ myMockSpotMarkets[1].initialAssetWeight =
333
+ (SPOT_MARKET_WEIGHT_PRECISION.toNumber() * 8) / 10; // 0.8
334
+ myMockSpotMarkets[1].maintenanceAssetWeight =
335
+ (SPOT_MARKET_WEIGHT_PRECISION.toNumber() * 9) / 10; // 0.9
336
+ myMockSpotMarkets[1].initialLiabilityWeight =
337
+ (SPOT_MARKET_WEIGHT_PRECISION.toNumber() * 12) / 10; // 1.2
338
+ myMockSpotMarkets[1].maintenanceLiabilityWeight =
339
+ (SPOT_MARKET_WEIGHT_PRECISION.toNumber() * 11) / 10; // 1.1
340
+
341
+ // ---------- Cross margin only (spot positions) ----------
342
+ const crossAccount = _.cloneDeep(baseMockUserAccount);
343
+ // USDC deposit: $20,000
344
+ crossAccount.spotPositions[0].marketIndex = 0;
345
+ crossAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT;
346
+ crossAccount.spotPositions[0].scaledBalance = new BN(20000).mul(
347
+ SPOT_MARKET_BALANCE_PRECISION
348
+ );
349
+ // SOL borrow: 100 units
350
+ crossAccount.spotPositions[1].marketIndex = 1;
351
+ crossAccount.spotPositions[1].balanceType = SpotBalanceType.BORROW;
352
+ crossAccount.spotPositions[1].scaledBalance = new BN(100).mul(
353
+ SPOT_MARKET_BALANCE_PRECISION
354
+ );
355
+ // No perp exposure in cross calc
356
+ crossAccount.perpPositions[0].baseAssetAmount = new BN(
357
+ 100 * BASE_PRECISION.toNumber()
358
+ );
359
+ crossAccount.perpPositions[0].quoteAssetAmount = new BN(
360
+ -11000 * QUOTE_PRECISION.toNumber()
361
+ );
362
+ crossAccount.perpPositions[0].positionFlag = PositionFlag.IsolatedPosition;
363
+ crossAccount.perpPositions[0].isolatedPositionScaledBalance = new BN(
364
+ 100
365
+ ).mul(SPOT_MARKET_BALANCE_PRECISION);
366
+
367
+ const userCross: User = await makeMockUser(
368
+ myMockPerpMarkets,
369
+ myMockSpotMarkets,
370
+ crossAccount,
371
+ [100, 1, 1, 1, 1, 1, 1, 1], // perp oracle for market 0 = 100
372
+ [1, 100, 1, 1, 1, 1, 1, 1] // spot oracle: usdc=1, sol=100
373
+ );
374
+
375
+ const crossCalc = userCross.getMarginCalculation('Initial');
376
+ const isolatedMarginCalc = crossCalc.isolatedMarginCalculations.get(0);
377
+ // Expect: cross MR from SOL borrow: 100 * $100 = $10,000 * 1.2 = $12,000
378
+ assert(crossCalc.marginRequirement.eq(new BN('12000000000')));
379
+ // Expect: cross total collateral from USDC deposit only = $20,000
380
+ assert(crossCalc.totalCollateral.eq(new BN('20000000000')));
381
+ // Meets cross margin requirement
382
+ assert(crossCalc.marginRequirement.lte(crossCalc.totalCollateral));
383
+
384
+ assert(isolatedMarginCalc?.marginRequirement.eq(new BN('1000000000')));
385
+ assert(isolatedMarginCalc?.totalCollateral.eq(new BN('-900000000')));
386
+ // With 10% buffer
387
+ const tenPct = new BN(1000);
388
+ const crossCalcBuf = userCross.getMarginCalculation('Initial', {
389
+ liquidationBuffer: tenPct,
390
+ });
391
+ assert(crossCalcBuf.marginRequirementPlusBuffer.eq(new BN('13000000000'))); // replicate 10% buffer
392
+ const crossTotalPlusBuffer = crossCalcBuf.totalCollateral.add(
393
+ crossCalcBuf.totalCollateralBuffer
394
+ );
395
+ assert(crossTotalPlusBuffer.eq(new BN('20000000000')));
396
+
397
+ const isoPosition = crossCalcBuf.isolatedMarginCalculations.get(0);
398
+ assert(isoPosition?.marginRequirementPlusBuffer.eq(new BN('2000000000')));
399
+ assert(
400
+ isoPosition?.totalCollateralBuffer
401
+ .add(isoPosition?.totalCollateral)
402
+ .eq(new BN('-1000000000'))
403
+ );
404
+ });
405
+ });
@@ -49,7 +49,6 @@ async function makeMockUser(
49
49
  oraclePriceMap[myMockSpotMarkets[i].oracle.toString()] =
50
50
  spotOraclePriceList[i];
51
51
  }
52
- // console.log(oraclePriceMap);
53
52
 
54
53
  function getMockUserAccount(): UserAccount {
55
54
  return myMockUserAccount;
@@ -61,12 +60,6 @@ async function makeMockUser(
61
60
  return myMockSpotMarkets[marketIndex];
62
61
  }
63
62
  function getMockOracle(oracleKey: PublicKey) {
64
- // console.log('oracleKey.toString():', oracleKey.toString());
65
- // console.log(
66
- // 'oraclePriceMap[oracleKey.toString()]:',
67
- // oraclePriceMap[oracleKey.toString()]
68
- // );
69
-
70
63
  const QUOTE_ORACLE_PRICE_DATA: OraclePriceData = {
71
64
  price: new BN(
72
65
  oraclePriceMap[oracleKey.toString()] * PRICE_PRECISION.toNumber()