@drift-labs/sdk 2.49.0-beta.9 → 2.52.0-beta.1

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 (75) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/{mockUserAccountSubscriber.d.ts → basicUserAccountSubscriber.d.ts} +6 -2
  3. package/lib/accounts/{mockUserAccountSubscriber.js → basicUserAccountSubscriber.js} +13 -6
  4. package/lib/accounts/bulkAccountLoader.js +7 -1
  5. package/lib/accounts/oneShotUserAccountSubscriber.d.ts +17 -0
  6. package/lib/accounts/oneShotUserAccountSubscriber.js +48 -0
  7. package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +1 -0
  8. package/lib/adminClient.d.ts +2 -0
  9. package/lib/adminClient.js +17 -0
  10. package/lib/constants/numericConstants.d.ts +3 -0
  11. package/lib/constants/numericConstants.js +4 -1
  12. package/lib/constants/perpMarkets.js +40 -0
  13. package/lib/constants/spotMarkets.js +10 -0
  14. package/lib/decode/user.d.ts +3 -0
  15. package/lib/decode/user.js +329 -0
  16. package/lib/driftClient.d.ts +6 -1
  17. package/lib/driftClient.js +35 -10
  18. package/lib/examples/loadDlob.js +10 -5
  19. package/lib/idl/drift.json +32 -2
  20. package/lib/index.d.ts +3 -1
  21. package/lib/index.js +3 -1
  22. package/lib/math/state.d.ts +5 -0
  23. package/lib/math/state.js +27 -0
  24. package/lib/math/superStake.d.ts +43 -0
  25. package/lib/math/superStake.js +64 -22
  26. package/lib/orderSubscriber/OrderSubscriber.d.ts +3 -0
  27. package/lib/orderSubscriber/OrderSubscriber.js +17 -3
  28. package/lib/orderSubscriber/WebsocketSubscription.d.ts +1 -1
  29. package/lib/orderSubscriber/WebsocketSubscription.js +8 -6
  30. package/lib/orderSubscriber/types.d.ts +4 -1
  31. package/lib/types.d.ts +2 -1
  32. package/lib/user.d.ts +2 -1
  33. package/lib/user.js +13 -5
  34. package/lib/userMap/PollingSubscription.d.ts +15 -0
  35. package/lib/userMap/PollingSubscription.js +28 -0
  36. package/lib/userMap/WebsocketSubscription.d.ts +23 -0
  37. package/lib/userMap/WebsocketSubscription.js +41 -0
  38. package/lib/userMap/userMap.d.ts +16 -18
  39. package/lib/userMap/userMap.js +73 -34
  40. package/lib/userMap/userMapConfig.d.ts +21 -0
  41. package/lib/userMap/userMapConfig.js +2 -0
  42. package/lib/userStats.d.ts +1 -0
  43. package/lib/userStats.js +3 -0
  44. package/package.json +2 -1
  45. package/src/accounts/{mockUserAccountSubscriber.ts → basicUserAccountSubscriber.ts} +12 -6
  46. package/src/accounts/bulkAccountLoader.ts +10 -3
  47. package/src/accounts/oneShotUserAccountSubscriber.ts +64 -0
  48. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +1 -0
  49. package/src/adminClient.ts +27 -0
  50. package/src/constants/numericConstants.ts +4 -0
  51. package/src/constants/perpMarkets.ts +40 -0
  52. package/src/constants/spotMarkets.ts +10 -0
  53. package/src/decode/user.ts +358 -0
  54. package/src/driftClient.ts +62 -15
  55. package/src/examples/loadDlob.ts +11 -6
  56. package/src/idl/drift.json +33 -3
  57. package/src/index.ts +3 -1
  58. package/src/math/state.ts +26 -0
  59. package/src/math/superStake.ts +108 -20
  60. package/src/orderSubscriber/OrderSubscriber.ts +33 -7
  61. package/src/orderSubscriber/WebsocketSubscription.ts +17 -16
  62. package/src/orderSubscriber/types.ts +15 -2
  63. package/src/types.ts +2 -1
  64. package/src/user.ts +19 -6
  65. package/src/userMap/PollingSubscription.ts +48 -0
  66. package/src/userMap/WebsocketSubscription.ts +76 -0
  67. package/src/userMap/userMap.ts +105 -70
  68. package/src/userMap/userMapConfig.ts +34 -0
  69. package/src/userStats.ts +8 -0
  70. package/tests/amm/test.ts +6 -3
  71. package/tests/decode/test.ts +266 -0
  72. package/tests/decode/userAccountBufferStrings.ts +102 -0
  73. package/tests/dlob/helpers.ts +1 -0
  74. package/tests/dlob/test.ts +10 -8
  75. 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
+ OneShotUserAccountSubscriber,
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,27 @@ 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 OneShotUserAccountSubscriber(
136
+ this.driftClient.program,
137
+ userAccountPublicKey,
138
+ userAccount,
139
+ slot,
140
+ this.commitment
141
+ ),
142
+ },
114
143
  });
115
144
  await user.subscribe(userAccount);
116
145
  this.userMap.set(userAccountPublicKey.toString(), user);
@@ -216,14 +245,18 @@ export class UserMap implements UserMapInterface {
216
245
  return this.userMap.size;
217
246
  }
218
247
 
219
- public getUniqueAuthorities(useSyncCallbackCriteria = true): PublicKey[] {
248
+ /**
249
+ * Returns a unique list of authorities for all users in the UserMap that meet the filter criteria
250
+ * @param filterCriteria: Users must meet these criteria to be included
251
+ * @returns
252
+ */
253
+ public getUniqueAuthorities(
254
+ filterCriteria?: UserFilterCriteria
255
+ ): PublicKey[] {
220
256
  const usersMeetingCriteria = Array.from(this.userMap.values()).filter(
221
257
  (user) => {
222
258
  let pass = true;
223
- if (
224
- useSyncCallbackCriteria &&
225
- this.syncCallbackCriteria.hasOpenOrders
226
- ) {
259
+ if (filterCriteria && filterCriteria.hasOpenOrders) {
227
260
  pass = pass && user.getUserAccount().hasOpenOrder;
228
261
  }
229
262
  return pass;
@@ -257,7 +290,7 @@ export class UserMap implements UserMapInterface {
257
290
  const rpcRequestArgs = [
258
291
  this.driftClient.program.programId.toBase58(),
259
292
  {
260
- commitment: this.driftClient.connection.commitment,
293
+ commitment: this.commitment,
261
294
  filters,
262
295
  encoding: 'base64',
263
296
  withContext: true,
@@ -266,10 +299,7 @@ export class UserMap implements UserMapInterface {
266
299
 
267
300
  const rpcJSONResponse: any =
268
301
  // @ts-ignore
269
- await this.driftClient.connection._rpcRequest(
270
- 'getProgramAccounts',
271
- rpcRequestArgs
272
- );
302
+ await this.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
273
303
 
274
304
  const rpcResponseAndContext: RpcResponseAndContext<
275
305
  Array<{
@@ -296,34 +326,24 @@ export class UserMap implements UserMapInterface {
296
326
 
297
327
  for (const [key, buffer] of programAccountBufferMap.entries()) {
298
328
  if (!this.has(key)) {
299
- const userAccount =
300
- this.driftClient.program.account.user.coder.accounts.decode(
301
- 'User',
302
- buffer
303
- );
329
+ const userAccount = this.decode('User', buffer);
304
330
  await this.addPubkey(new PublicKey(key), userAccount);
331
+ this.userMap.get(key).accountSubscriber.updateData(userAccount, slot);
305
332
  }
333
+ // give event loop a chance to breathe
334
+ await new Promise((resolve) => setTimeout(resolve, 0));
306
335
  }
307
336
 
308
337
  for (const [key, user] of this.userMap.entries()) {
309
338
  if (!programAccountBufferMap.has(key)) {
310
339
  await user.unsubscribe();
311
340
  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
341
  }
320
- }
321
-
322
- if (this.syncCallback) {
323
- await this.syncCallback(this.getUniqueAuthorities());
342
+ // give event loop a chance to breathe
343
+ await new Promise((resolve) => setTimeout(resolve, 0));
324
344
  }
325
345
  } catch (e) {
326
- console.error(`Error in UserMap.sync()`);
346
+ console.error(`Error in UserMap.sync():`);
327
347
  console.error(e);
328
348
  } finally {
329
349
  this.syncPromiseResolver();
@@ -332,6 +352,8 @@ export class UserMap implements UserMapInterface {
332
352
  }
333
353
 
334
354
  public async unsubscribe() {
355
+ await this.subscription.unsubscribe();
356
+
335
357
  for (const [key, user] of this.userMap.entries()) {
336
358
  await user.unsubscribe();
337
359
  this.userMap.delete(key);
@@ -345,4 +367,17 @@ export class UserMap implements UserMapInterface {
345
367
  this.lastNumberOfSubAccounts = undefined;
346
368
  }
347
369
  }
370
+
371
+ public async updateUserAccount(
372
+ key: string,
373
+ userAccount: UserAccount,
374
+ slot: number
375
+ ) {
376
+ if (!this.userMap.has(key)) {
377
+ this.addPubkey(new PublicKey(key), userAccount, slot);
378
+ } else {
379
+ const user = this.userMap.get(key);
380
+ user.accountSubscriber.updateData(userAccount, slot);
381
+ }
382
+ }
348
383
  }
@@ -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/src/userStats.ts CHANGED
@@ -82,4 +82,12 @@ export class UserStats {
82
82
  };
83
83
  }
84
84
  }
85
+
86
+ public static getOldestActionTs(account: UserStatsAccount): number {
87
+ return Math.min(
88
+ account.lastFillerVolume30DTs.toNumber(),
89
+ account.lastMakerVolume30DTs.toNumber(),
90
+ account.lastTakerVolume30DTs.toNumber()
91
+ );
92
+ }
85
93
  }
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
+ }