@drift-labs/sdk 2.49.0-beta.8 → 2.52.0-beta.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 (64) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/{mockUserAccountSubscriber.d.ts → basicUserAccountSubscriber.d.ts} +2 -2
  3. package/lib/accounts/{mockUserAccountSubscriber.js → basicUserAccountSubscriber.js} +9 -6
  4. package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +0 -1
  5. package/lib/adminClient.d.ts +1 -0
  6. package/lib/adminClient.js +8 -0
  7. package/lib/constants/perpMarkets.js +40 -0
  8. package/lib/constants/spotMarkets.js +10 -0
  9. package/lib/decode/user.d.ts +3 -0
  10. package/lib/decode/user.js +329 -0
  11. package/lib/driftClient.d.ts +5 -1
  12. package/lib/driftClient.js +28 -7
  13. package/lib/examples/loadDlob.js +10 -5
  14. package/lib/idl/drift.json +32 -2
  15. package/lib/index.d.ts +2 -1
  16. package/lib/index.js +2 -1
  17. package/lib/math/state.d.ts +5 -0
  18. package/lib/math/state.js +27 -0
  19. package/lib/math/superStake.d.ts +43 -0
  20. package/lib/math/superStake.js +64 -22
  21. package/lib/orderSubscriber/OrderSubscriber.d.ts +3 -0
  22. package/lib/orderSubscriber/OrderSubscriber.js +17 -3
  23. package/lib/orderSubscriber/WebsocketSubscription.d.ts +1 -1
  24. package/lib/orderSubscriber/WebsocketSubscription.js +8 -6
  25. package/lib/orderSubscriber/types.d.ts +4 -1
  26. package/lib/types.d.ts +2 -1
  27. package/lib/user.d.ts +2 -1
  28. package/lib/user.js +13 -5
  29. package/lib/userMap/PollingSubscription.d.ts +15 -0
  30. package/lib/userMap/PollingSubscription.js +28 -0
  31. package/lib/userMap/WebsocketSubscription.d.ts +23 -0
  32. package/lib/userMap/WebsocketSubscription.js +41 -0
  33. package/lib/userMap/userMap.d.ts +16 -18
  34. package/lib/userMap/userMap.js +73 -34
  35. package/lib/userMap/userMapConfig.d.ts +21 -0
  36. package/lib/userMap/userMapConfig.js +2 -0
  37. package/package.json +2 -1
  38. package/src/accounts/{mockUserAccountSubscriber.ts → basicUserAccountSubscriber.ts} +8 -6
  39. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +0 -1
  40. package/src/adminClient.ts +14 -0
  41. package/src/constants/perpMarkets.ts +40 -0
  42. package/src/constants/spotMarkets.ts +10 -0
  43. package/src/decode/user.ts +358 -0
  44. package/src/driftClient.ts +48 -7
  45. package/src/examples/loadDlob.ts +11 -6
  46. package/src/idl/drift.json +33 -3
  47. package/src/index.ts +2 -1
  48. package/src/math/state.ts +26 -0
  49. package/src/math/superStake.ts +108 -20
  50. package/src/orderSubscriber/OrderSubscriber.ts +33 -7
  51. package/src/orderSubscriber/WebsocketSubscription.ts +17 -16
  52. package/src/orderSubscriber/types.ts +15 -2
  53. package/src/types.ts +2 -1
  54. package/src/user.ts +19 -6
  55. package/src/userMap/PollingSubscription.ts +48 -0
  56. package/src/userMap/WebsocketSubscription.ts +76 -0
  57. package/src/userMap/userMap.ts +103 -70
  58. package/src/userMap/userMapConfig.ts +34 -0
  59. package/tests/amm/test.ts +6 -3
  60. package/tests/decode/test.ts +266 -0
  61. package/tests/decode/userAccountBufferStrings.ts +102 -0
  62. package/tests/dlob/helpers.ts +1 -0
  63. package/tests/dlob/test.ts +10 -8
  64. package/tests/user/helpers.ts +0 -1
@@ -3,7 +3,6 @@ import {
3
3
  DriftClient,
4
4
  UserAccount,
5
5
  OrderRecord,
6
- UserSubscriptionConfig,
7
6
  WrappedEvent,
8
7
  DepositRecord,
9
8
  FundingPaymentRecord,
@@ -14,11 +13,25 @@ import {
14
13
  LPRecord,
15
14
  StateAccount,
16
15
  DLOB,
16
+ BasicUserAccountSubscriber,
17
+ BN,
17
18
  } from '..';
18
19
 
19
- import { PublicKey, RpcResponseAndContext } from '@solana/web3.js';
20
+ import {
21
+ Commitment,
22
+ Connection,
23
+ PublicKey,
24
+ RpcResponseAndContext,
25
+ } from '@solana/web3.js';
20
26
  import { Buffer } from 'buffer';
21
27
  import { getNonIdleUserFilter, getUserFilter } from '../memcmp';
28
+ import {
29
+ UserAccountFilterCriteria as UserFilterCriteria,
30
+ UserMapConfig,
31
+ } from './userMapConfig';
32
+ import { WebsocketSubscription } from './WebsocketSubscription';
33
+ import { PollingSubscription } from './PollingSubscription';
34
+ import { decodeUser } from '../decode/user';
22
35
 
23
36
  export interface UserMapInterface {
24
37
  subscribe(): Promise<void>;
@@ -32,59 +45,65 @@ export interface UserMapInterface {
32
45
  values(): IterableIterator<User>;
33
46
  }
34
47
 
35
- // filter users that meet these criteria when passing into syncCallback
36
- export type SyncCallbackCriteria = {
37
- // only sync users that have open orders
38
- hasOpenOrders: boolean;
39
- };
40
-
41
48
  export class UserMap implements UserMapInterface {
42
49
  private userMap = new Map<string, User>();
43
- private driftClient: DriftClient;
44
- private accountSubscription: UserSubscriptionConfig;
50
+ driftClient: DriftClient;
51
+ private connection: Connection;
52
+ private commitment: Commitment;
45
53
  private includeIdle: boolean;
46
- private lastNumberOfSubAccounts;
54
+ private lastNumberOfSubAccounts: BN;
55
+ private subscription: PollingSubscription | WebsocketSubscription;
47
56
  private stateAccountUpdateCallback = async (state: StateAccount) => {
48
- if (state.numberOfSubAccounts !== this.lastNumberOfSubAccounts) {
57
+ if (!state.numberOfSubAccounts.eq(this.lastNumberOfSubAccounts)) {
49
58
  await this.sync();
50
59
  this.lastNumberOfSubAccounts = state.numberOfSubAccounts;
51
60
  }
52
61
  };
53
- private syncCallback: (authorities: PublicKey[]) => Promise<void>;
54
- private syncCallbackCriteria: SyncCallbackCriteria;
62
+ private decode;
55
63
 
56
64
  private syncPromise?: Promise<void>;
57
65
  private syncPromiseResolver: () => void;
58
66
 
59
67
  /**
60
68
  * Constructs a new UserMap instance.
61
- *
62
- * @param {DriftClient} driftClient - The DriftClient instance.
63
- * @param {UserSubscriptionConfig} accountSubscription - The UserSubscriptionConfig instance.
64
- * @param {boolean} includeIdle - Whether idle users are subscribed to. Defaults to false to decrease # of user subscriptions.
65
- * @param {(authorities: PublicKey[]) => Promise<void>} syncCallback - Called after `sync` completes, will pas in unique list of authorities. Useful for using it to sync UserStatsMap.
66
- * @param {SyncCallbackCriteria} syncCallbackCriteria - The criteria for the sync callback. Defaults to having no filters
67
69
  */
68
- constructor(
69
- driftClient: DriftClient,
70
- accountSubscription: UserSubscriptionConfig,
71
- includeIdle = false,
72
- syncCallback?: (authorities: PublicKey[]) => Promise<void>,
73
- syncCallbackCriteria: SyncCallbackCriteria = { hasOpenOrders: false }
74
- ) {
75
- this.driftClient = driftClient;
76
- this.accountSubscription = accountSubscription;
77
- this.includeIdle = includeIdle;
78
- this.syncCallback = syncCallback;
79
- this.syncCallbackCriteria = syncCallbackCriteria;
80
- }
81
-
82
- public addSyncCallback(
83
- syncCallback?: (authorities: PublicKey[]) => Promise<void>,
84
- syncCallbackCriteria: SyncCallbackCriteria = { hasOpenOrders: false }
85
- ) {
86
- this.syncCallback = syncCallback;
87
- this.syncCallbackCriteria = syncCallbackCriteria;
70
+ constructor(config: UserMapConfig) {
71
+ this.driftClient = config.driftClient;
72
+ if (config.connection) {
73
+ this.connection = config.connection;
74
+ } else {
75
+ this.connection = this.driftClient.connection;
76
+ }
77
+ this.commitment =
78
+ config.subscriptionConfig.commitment ?? this.driftClient.opts.commitment;
79
+ this.includeIdle = config.includeIdle ?? false;
80
+
81
+ let decodeFn;
82
+ if (config.fastDecode ?? true) {
83
+ decodeFn = (name, buffer) => decodeUser(buffer);
84
+ } else {
85
+ decodeFn =
86
+ this.driftClient.program.account.user.coder.accounts.decodeUnchecked.bind(
87
+ this.driftClient.program.account.user.coder.accounts
88
+ );
89
+ }
90
+ this.decode = decodeFn;
91
+
92
+ if (config.subscriptionConfig.type === 'polling') {
93
+ this.subscription = new PollingSubscription({
94
+ userMap: this,
95
+ frequency: config.subscriptionConfig.frequency,
96
+ skipInitialLoad: config.skipInitialLoad,
97
+ });
98
+ } else {
99
+ this.subscription = new WebsocketSubscription({
100
+ userMap: this,
101
+ commitment: this.commitment,
102
+ resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
103
+ skipInitialLoad: config.skipInitialLoad,
104
+ decodeFn,
105
+ });
106
+ }
88
107
  }
89
108
 
90
109
  public async subscribe() {
@@ -100,17 +119,25 @@ export class UserMap implements UserMapInterface {
100
119
  this.stateAccountUpdateCallback
101
120
  );
102
121
 
103
- await this.sync();
122
+ await this.subscription.subscribe();
104
123
  }
105
124
 
106
125
  public async addPubkey(
107
126
  userAccountPublicKey: PublicKey,
108
- userAccount?: UserAccount
127
+ userAccount?: UserAccount,
128
+ slot?: number
109
129
  ) {
110
130
  const user = new User({
111
131
  driftClient: this.driftClient,
112
132
  userAccountPublicKey,
113
- accountSubscription: this.accountSubscription,
133
+ accountSubscription: {
134
+ type: 'custom',
135
+ userAccountSubscriber: new BasicUserAccountSubscriber(
136
+ userAccountPublicKey,
137
+ userAccount,
138
+ slot
139
+ ),
140
+ },
114
141
  });
115
142
  await user.subscribe(userAccount);
116
143
  this.userMap.set(userAccountPublicKey.toString(), user);
@@ -216,14 +243,18 @@ export class UserMap implements UserMapInterface {
216
243
  return this.userMap.size;
217
244
  }
218
245
 
219
- public getUniqueAuthorities(useSyncCallbackCriteria = true): PublicKey[] {
246
+ /**
247
+ * Returns a unique list of authorities for all users in the UserMap that meet the filter criteria
248
+ * @param filterCriteria: Users must meet these criteria to be included
249
+ * @returns
250
+ */
251
+ public getUniqueAuthorities(
252
+ filterCriteria?: UserFilterCriteria
253
+ ): PublicKey[] {
220
254
  const usersMeetingCriteria = Array.from(this.userMap.values()).filter(
221
255
  (user) => {
222
256
  let pass = true;
223
- if (
224
- useSyncCallbackCriteria &&
225
- this.syncCallbackCriteria.hasOpenOrders
226
- ) {
257
+ if (filterCriteria && filterCriteria.hasOpenOrders) {
227
258
  pass = pass && user.getUserAccount().hasOpenOrder;
228
259
  }
229
260
  return pass;
@@ -257,7 +288,7 @@ export class UserMap implements UserMapInterface {
257
288
  const rpcRequestArgs = [
258
289
  this.driftClient.program.programId.toBase58(),
259
290
  {
260
- commitment: this.driftClient.connection.commitment,
291
+ commitment: this.commitment,
261
292
  filters,
262
293
  encoding: 'base64',
263
294
  withContext: true,
@@ -266,10 +297,7 @@ export class UserMap implements UserMapInterface {
266
297
 
267
298
  const rpcJSONResponse: any =
268
299
  // @ts-ignore
269
- await this.driftClient.connection._rpcRequest(
270
- 'getProgramAccounts',
271
- rpcRequestArgs
272
- );
300
+ await this.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
273
301
 
274
302
  const rpcResponseAndContext: RpcResponseAndContext<
275
303
  Array<{
@@ -296,34 +324,24 @@ export class UserMap implements UserMapInterface {
296
324
 
297
325
  for (const [key, buffer] of programAccountBufferMap.entries()) {
298
326
  if (!this.has(key)) {
299
- const userAccount =
300
- this.driftClient.program.account.user.coder.accounts.decode(
301
- 'User',
302
- buffer
303
- );
327
+ const userAccount = this.decode('User', buffer);
304
328
  await this.addPubkey(new PublicKey(key), userAccount);
329
+ this.userMap.get(key).accountSubscriber.updateData(userAccount, slot);
305
330
  }
331
+ // give event loop a chance to breathe
332
+ await new Promise((resolve) => setTimeout(resolve, 0));
306
333
  }
307
334
 
308
335
  for (const [key, user] of this.userMap.entries()) {
309
336
  if (!programAccountBufferMap.has(key)) {
310
337
  await user.unsubscribe();
311
338
  this.userMap.delete(key);
312
- } else {
313
- const userAccount =
314
- this.driftClient.program.account.user.coder.accounts.decode(
315
- 'User',
316
- programAccountBufferMap.get(key)
317
- );
318
- user.accountSubscriber.updateData(userAccount, slot);
319
339
  }
320
- }
321
-
322
- if (this.syncCallback) {
323
- await this.syncCallback(this.getUniqueAuthorities());
340
+ // give event loop a chance to breathe
341
+ await new Promise((resolve) => setTimeout(resolve, 0));
324
342
  }
325
343
  } catch (e) {
326
- console.error(`Error in UserMap.sync()`);
344
+ console.error(`Error in UserMap.sync():`);
327
345
  console.error(e);
328
346
  } finally {
329
347
  this.syncPromiseResolver();
@@ -332,6 +350,8 @@ export class UserMap implements UserMapInterface {
332
350
  }
333
351
 
334
352
  public async unsubscribe() {
353
+ await this.subscription.unsubscribe();
354
+
335
355
  for (const [key, user] of this.userMap.entries()) {
336
356
  await user.unsubscribe();
337
357
  this.userMap.delete(key);
@@ -345,4 +365,17 @@ export class UserMap implements UserMapInterface {
345
365
  this.lastNumberOfSubAccounts = undefined;
346
366
  }
347
367
  }
368
+
369
+ public async updateUserAccount(
370
+ key: string,
371
+ userAccount: UserAccount,
372
+ slot: number
373
+ ) {
374
+ if (!this.userMap.has(key)) {
375
+ this.addPubkey(new PublicKey(key), userAccount, slot);
376
+ } else {
377
+ const user = this.userMap.get(key);
378
+ user.accountSubscriber.updateData(userAccount, slot);
379
+ }
380
+ }
348
381
  }
@@ -0,0 +1,34 @@
1
+ import { Commitment, Connection } from '@solana/web3.js';
2
+ import { DriftClient } from '../driftClient';
3
+
4
+ // passed into UserMap.getUniqueAuthorities to filter users
5
+ export type UserAccountFilterCriteria = {
6
+ // only return users that have open orders
7
+ hasOpenOrders: boolean;
8
+ };
9
+
10
+ export type UserMapConfig = {
11
+ driftClient: DriftClient;
12
+ // connection object to use specifically for the UserMap. If undefined, will use the driftClient's connection
13
+ connection?: Connection;
14
+ subscriptionConfig:
15
+ | {
16
+ type: 'polling';
17
+ frequency: number;
18
+ commitment?: Commitment;
19
+ }
20
+ | {
21
+ type: 'websocket';
22
+ resubTimeoutMs?: number;
23
+ commitment?: Commitment;
24
+ };
25
+
26
+ // True to skip the initial load of userAccounts via getProgramAccounts
27
+ skipInitialLoad?: boolean;
28
+
29
+ // True to include idle users when loading. Defaults to false to decrease # of accounts subscribed to.
30
+ includeIdle?: boolean;
31
+
32
+ // Whether to skip loading available perp/spot positions and open orders
33
+ fastDecode?: boolean;
34
+ };
package/tests/amm/test.ts CHANGED
@@ -361,7 +361,10 @@ describe('AMM Tests', () => {
361
361
 
362
362
  console.log(terms2);
363
363
  assert(terms2.effectiveLeverageCapped <= 1.000001);
364
- assert(terms2.inventorySpreadScale == 1.013527);
364
+ assert(
365
+ terms2.inventorySpreadScale == 1.013527,
366
+ `got: ${terms2.inventorySpreadScale}`
367
+ );
365
368
  assert(terms2.longSpread == 1146);
366
369
  assert(terms2.shortSpread == 6686);
367
370
  });
@@ -466,7 +469,7 @@ describe('AMM Tests', () => {
466
469
 
467
470
  assert(markTwapLive.eq(new BN('1949826')));
468
471
  assert(oracleTwapLive.eq(new BN('1942510')));
469
- assert(est1.eq(new BN('15692')));
472
+ assert(est1.eq(new BN('15692')), `got: ${est1}`);
470
473
  assert(est2.eq(new BN('15692')));
471
474
  });
472
475
 
@@ -556,7 +559,7 @@ describe('AMM Tests', () => {
556
559
  assert(markTwapLive.eq(new BN('1222131')));
557
560
  assert(oracleTwapLive.eq(new BN('1222586')));
558
561
  assert(est1.eq(est2));
559
- assert(est2.eq(new BN('-1550')));
562
+ assert(est2.eq(new BN('-1550')), `got: ${est2}`);
560
563
  });
561
564
 
562
565
  it('orderbook L2 gen (no topOfBookQuoteAmounts, 10 numOrders, low liquidity)', async () => {
@@ -0,0 +1,266 @@
1
+ import { AnchorProvider, Idl, Program } from '@coral-xyz/anchor';
2
+ import driftIDL from '../../src/idl/drift.json';
3
+ import { Connection, Keypair } from '@solana/web3.js';
4
+ import { Wallet } from '../../src';
5
+ import {
6
+ DRIFT_PROGRAM_ID,
7
+ isSpotPositionAvailable,
8
+ isVariant,
9
+ Order,
10
+ PerpPosition,
11
+ positionIsAvailable,
12
+ SpotPosition,
13
+ } from '../../lib';
14
+ import { decodeUser } from '../../lib/decode/user';
15
+ import { assert } from 'chai';
16
+ import { userAccountBufferStrings } from './userAccountBufferStrings';
17
+ const sizeof = require('object-sizeof');
18
+
19
+ describe('Custom user decode', () => {
20
+ it('test', async () => {
21
+ const connection = new Connection('http://localhost:8899');
22
+ const wallet = new Wallet(new Keypair());
23
+ // @ts-ignore
24
+ const provider = new AnchorProvider(connection, wallet);
25
+ const program = new Program(driftIDL as Idl, DRIFT_PROGRAM_ID, provider);
26
+
27
+ let totalAnchorSize = 0;
28
+ let totalCustomSize = 0;
29
+ let totalAnchorTime = 0;
30
+ let totalCustomTime = 0;
31
+ for (const [
32
+ i,
33
+ userAccountBufferString,
34
+ ] of userAccountBufferStrings.entries()) {
35
+ const userAccountBuffer = Buffer.from(userAccountBufferString, 'base64');
36
+ const [anchorSize, customSize, anchorTime, customTime] =
37
+ testUserAccountDecode(program, userAccountBuffer, i);
38
+ totalAnchorSize += anchorSize;
39
+ totalCustomSize += customSize;
40
+ totalAnchorTime += anchorTime;
41
+ totalCustomTime += customTime;
42
+ }
43
+
44
+ console.log(`Total anchor size: ${totalAnchorSize}`);
45
+ console.log(`Total custom size: ${totalCustomSize}`);
46
+ console.log(`Total anchor time: ${totalAnchorTime}`);
47
+ console.log(`Total custom size: ${totalCustomTime}`);
48
+ });
49
+ });
50
+
51
+ function testUserAccountDecode(program: Program, buffer: Buffer, i: number) {
52
+ console.log(`Testing user account decode ${i}`);
53
+
54
+ const anchorStartTimestamp = Date.now();
55
+ const anchorUserAccount = program.coder.accounts.decode('User', buffer);
56
+ const anchorEndTimestamp = Date.now();
57
+ const anchorTime = anchorEndTimestamp - anchorStartTimestamp;
58
+
59
+ const customStartTimestamp = Date.now();
60
+ const customUserAccount = decodeUser(buffer);
61
+ const customEndTimestamp = Date.now();
62
+ const customTime = customEndTimestamp - customStartTimestamp;
63
+
64
+ const anchorSize = sizeof(anchorUserAccount);
65
+ const customSize = sizeof(customUserAccount);
66
+
67
+ assert(anchorUserAccount.authority.equals(customUserAccount.authority));
68
+ assert(anchorUserAccount.delegate.equals(customUserAccount.delegate));
69
+ assert(arraysAreEqual(anchorUserAccount.name, customUserAccount.name));
70
+
71
+ const anchorSpotPositionGenerator = getSpotPositions(
72
+ anchorUserAccount.spotPositions
73
+ );
74
+ const customSpotPositionGenerator = getSpotPositions(
75
+ customUserAccount.spotPositions
76
+ );
77
+ for (const [anchorSpotPosition, customSpotPosition] of zipGenerator(
78
+ anchorSpotPositionGenerator,
79
+ customSpotPositionGenerator
80
+ )) {
81
+ testSpotPosition(anchorSpotPosition, customSpotPosition);
82
+ }
83
+
84
+ const anchorPerpPositionGenerator = getPerpPositions(
85
+ anchorUserAccount.perpPositions
86
+ );
87
+ const customPerpPositionGenerator = getPerpPositions(
88
+ customUserAccount.perpPositions
89
+ );
90
+ for (const [anchorPerpPosition, customPerpPosition] of zipGenerator(
91
+ anchorPerpPositionGenerator,
92
+ customPerpPositionGenerator
93
+ )) {
94
+ testPerpPosition(anchorPerpPosition, customPerpPosition);
95
+ }
96
+
97
+ const anchorOrderGenerator = getOrders(anchorUserAccount.orders);
98
+ const customOrderGenerator = getOrders(customUserAccount.orders);
99
+ for (const [anchorOrder, customOrder] of zipGenerator(
100
+ anchorOrderGenerator,
101
+ customOrderGenerator
102
+ )) {
103
+ testOrder(anchorOrder, customOrder);
104
+ }
105
+
106
+ assert(
107
+ anchorUserAccount.lastAddPerpLpSharesTs.eq(
108
+ customUserAccount.lastAddPerpLpSharesTs
109
+ )
110
+ );
111
+ assert(anchorUserAccount.totalDeposits.eq(customUserAccount.totalDeposits));
112
+ assert(anchorUserAccount.totalWithdraws.eq(customUserAccount.totalWithdraws));
113
+ assert(
114
+ anchorUserAccount.totalSocialLoss.eq(customUserAccount.totalSocialLoss)
115
+ );
116
+ assert(anchorUserAccount.settledPerpPnl.eq(customUserAccount.settledPerpPnl));
117
+ assert(
118
+ anchorUserAccount.cumulativeSpotFees.eq(
119
+ customUserAccount.cumulativeSpotFees
120
+ )
121
+ );
122
+ assert(
123
+ anchorUserAccount.cumulativePerpFunding.eq(
124
+ customUserAccount.cumulativePerpFunding
125
+ )
126
+ );
127
+ assert(
128
+ anchorUserAccount.liquidationMarginFreed.eq(
129
+ customUserAccount.liquidationMarginFreed
130
+ )
131
+ );
132
+ assert(anchorUserAccount.lastActiveSlot.eq(customUserAccount.lastActiveSlot));
133
+ assert(anchorUserAccount.subAccountId === customUserAccount.subAccountId);
134
+ assert(anchorUserAccount.status === customUserAccount.status);
135
+ assert(
136
+ anchorUserAccount.nextLiquidationId === customUserAccount.nextLiquidationId
137
+ );
138
+ assert(anchorUserAccount.nextOrderId === customUserAccount.nextOrderId);
139
+ assert(anchorUserAccount.maxMarginRatio === customUserAccount.maxMarginRatio);
140
+ assert(
141
+ anchorUserAccount.isMarginTradingEnabled ===
142
+ customUserAccount.isMarginTradingEnabled
143
+ );
144
+ assert(anchorUserAccount.idle === customUserAccount.idle);
145
+ assert(anchorUserAccount.openOrders === customUserAccount.openOrders);
146
+ assert(anchorUserAccount.hasOpenOrder === customUserAccount.hasOpenOrder);
147
+ assert(anchorUserAccount.openAuctions === customUserAccount.openAuctions);
148
+ assert(anchorUserAccount.hasOpenAuction === customUserAccount.hasOpenAuction);
149
+
150
+ return [anchorSize, customSize, anchorTime, customTime];
151
+ }
152
+
153
+ function* getSpotPositions(spotPositions: SpotPosition[]) {
154
+ for (const spotPosition of spotPositions) {
155
+ if (!isSpotPositionAvailable(spotPosition)) {
156
+ yield spotPosition;
157
+ }
158
+ }
159
+ }
160
+
161
+ function testSpotPosition(anchor: SpotPosition, custom: SpotPosition) {
162
+ assert(anchor.marketIndex === custom.marketIndex);
163
+ assert(enumsAreEqual(anchor.balanceType, custom.balanceType));
164
+ assert(anchor.openOrders === custom.openOrders);
165
+ assert(anchor.scaledBalance.eq(custom.scaledBalance));
166
+ assert(anchor.openBids.eq(custom.openBids));
167
+ assert(anchor.openAsks.eq(custom.openAsks));
168
+ assert(anchor.cumulativeDeposits.eq(custom.cumulativeDeposits));
169
+ }
170
+
171
+ function* getPerpPositions(perpPositions: PerpPosition[]) {
172
+ for (const perpPosition of perpPositions) {
173
+ if (!positionIsAvailable(perpPosition)) {
174
+ yield perpPosition;
175
+ }
176
+ }
177
+ }
178
+
179
+ function testPerpPosition(anchor: PerpPosition, custom: PerpPosition) {
180
+ assert(anchor.baseAssetAmount.eq(custom.baseAssetAmount));
181
+ assert(anchor.lastCumulativeFundingRate.eq(custom.lastCumulativeFundingRate));
182
+ assert(anchor.marketIndex === custom.marketIndex);
183
+ assert(anchor.quoteAssetAmount.eq(custom.quoteAssetAmount));
184
+ assert(anchor.quoteEntryAmount.eq(custom.quoteEntryAmount));
185
+ assert(anchor.quoteBreakEvenAmount.eq(custom.quoteBreakEvenAmount));
186
+ assert(anchor.openBids.eq(custom.openBids));
187
+ assert(anchor.openAsks.eq(custom.openAsks));
188
+ assert(anchor.settledPnl.eq(custom.settledPnl));
189
+ assert(anchor.lpShares.eq(custom.lpShares));
190
+ assert(anchor.lastBaseAssetAmountPerLp.eq(custom.lastBaseAssetAmountPerLp));
191
+ assert(anchor.lastQuoteAssetAmountPerLp.eq(custom.lastQuoteAssetAmountPerLp));
192
+ assert(anchor.openOrders === custom.openOrders);
193
+ assert(anchor.perLpBase === custom.perLpBase);
194
+ }
195
+
196
+ function* getOrders(orders: Order[]) {
197
+ for (const order of orders) {
198
+ if (isVariant(order.status, 'open')) {
199
+ yield order;
200
+ }
201
+ }
202
+ }
203
+
204
+ function testOrder(anchor: Order, custom: Order) {
205
+ assert(enumsAreEqual(anchor.status, custom.status));
206
+ assert(enumsAreEqual(anchor.orderType, custom.orderType));
207
+ assert(enumsAreEqual(anchor.marketType, custom.marketType));
208
+ assert(anchor.slot.eq(custom.slot));
209
+ assert(anchor.orderId === custom.orderId);
210
+ assert(anchor.userOrderId === custom.userOrderId);
211
+ assert(anchor.marketIndex === custom.marketIndex);
212
+ assert(anchor.price.eq(custom.price));
213
+ assert(anchor.baseAssetAmount.eq(custom.baseAssetAmount));
214
+ assert(anchor.baseAssetAmountFilled.eq(custom.baseAssetAmountFilled));
215
+ assert(anchor.quoteAssetAmountFilled.eq(custom.quoteAssetAmountFilled));
216
+ assert(enumsAreEqual(anchor.direction, custom.direction));
217
+ assert(anchor.reduceOnly === custom.reduceOnly);
218
+ assert(anchor.triggerPrice.eq(custom.triggerPrice));
219
+ assert(enumsAreEqual(anchor.triggerCondition, custom.triggerCondition));
220
+ assert(
221
+ enumsAreEqual(
222
+ anchor.existingPositionDirection,
223
+ custom.existingPositionDirection
224
+ )
225
+ );
226
+ assert(anchor.postOnly === custom.postOnly);
227
+ assert(anchor.immediateOrCancel === custom.immediateOrCancel);
228
+ assert(anchor.oraclePriceOffset === custom.oraclePriceOffset);
229
+ assert(anchor.auctionDuration === custom.auctionDuration);
230
+ assert(anchor.auctionStartPrice.eq(custom.auctionStartPrice));
231
+ assert(anchor.auctionEndPrice.eq(custom.auctionEndPrice));
232
+ assert(anchor.maxTs.eq(custom.maxTs));
233
+ }
234
+
235
+ function enumsAreEqual(e1: any, e2: any) {
236
+ return JSON.stringify(e1) === JSON.stringify(e2);
237
+ }
238
+
239
+ function arraysAreEqual(arr1, arr2) {
240
+ if (arr1.length !== arr2.length) {
241
+ return false;
242
+ }
243
+
244
+ for (let i = 0; i < arr1.length; i++) {
245
+ if (arr1[i] !== arr2[i]) {
246
+ return false;
247
+ }
248
+ }
249
+
250
+ return true;
251
+ }
252
+
253
+ function* zipGenerator(gen1, gen2) {
254
+ let iter1 = gen1.next();
255
+ let iter2 = gen2.next();
256
+
257
+ while (!iter1.done && !iter2.done) {
258
+ yield [iter1.value, iter2.value];
259
+ iter1 = gen1.next();
260
+ iter2 = gen2.next();
261
+ }
262
+
263
+ if (iter1.done !== iter2.done) {
264
+ throw new Error('Generators have different lengths');
265
+ }
266
+ }