@drift-labs/sdk 0.2.0-master.3 → 0.2.0-master.30

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 (208) hide show
  1. package/README.md +13 -13
  2. package/lib/accounts/bulkUserStatsSubscription.d.ts +7 -0
  3. package/lib/accounts/bulkUserStatsSubscription.js +21 -0
  4. package/lib/accounts/bulkUserSubscription.js +0 -1
  5. package/lib/accounts/fetch.d.ts +2 -1
  6. package/lib/accounts/fetch.js +9 -1
  7. package/lib/accounts/pollingClearingHouseAccountSubscriber.d.ts +16 -16
  8. package/lib/accounts/pollingClearingHouseAccountSubscriber.js +31 -28
  9. package/lib/accounts/pollingUserStatsAccountSubscriber.d.ts +27 -0
  10. package/lib/accounts/pollingUserStatsAccountSubscriber.js +113 -0
  11. package/lib/accounts/types.d.ts +22 -11
  12. package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +17 -17
  13. package/lib/accounts/webSocketClearingHouseAccountSubscriber.js +37 -35
  14. package/lib/accounts/webSocketUserStatsAccountSubsriber.d.ts +20 -0
  15. package/lib/accounts/webSocketUserStatsAccountSubsriber.js +47 -0
  16. package/lib/addresses/marketAddresses.d.ts +1 -3
  17. package/lib/addresses/pda.d.ts +10 -4
  18. package/lib/addresses/pda.js +51 -14
  19. package/lib/admin.d.ts +34 -22
  20. package/lib/admin.js +182 -73
  21. package/lib/clearingHouse.d.ts +120 -42
  22. package/lib/clearingHouse.js +1505 -254
  23. package/lib/clearingHouseConfig.d.ts +4 -4
  24. package/lib/clearingHouseUser.d.ts +50 -38
  25. package/lib/clearingHouseUser.js +410 -190
  26. package/lib/clearingHouseUserStats.d.ts +18 -0
  27. package/lib/clearingHouseUserStats.js +49 -0
  28. package/lib/clearingHouseUserStatsConfig.d.ts +14 -0
  29. package/lib/clearingHouseUserStatsConfig.js +2 -0
  30. package/lib/config.d.ts +7 -9
  31. package/lib/config.js +21 -21
  32. package/lib/constants/numericConstants.d.ts +18 -12
  33. package/lib/constants/numericConstants.js +28 -21
  34. package/lib/constants/perpMarkets.d.ts +18 -0
  35. package/lib/constants/{markets.js → perpMarkets.js} +7 -7
  36. package/lib/constants/spotMarkets.d.ts +19 -0
  37. package/lib/constants/spotMarkets.js +53 -0
  38. package/lib/dlob/DLOB.d.ts +73 -0
  39. package/lib/dlob/DLOB.js +553 -0
  40. package/lib/dlob/DLOBNode.d.ts +52 -0
  41. package/lib/dlob/DLOBNode.js +82 -0
  42. package/lib/dlob/NodeList.d.ts +26 -0
  43. package/lib/dlob/NodeList.js +138 -0
  44. package/lib/events/eventList.js +3 -0
  45. package/lib/events/eventSubscriber.d.ts +4 -2
  46. package/lib/events/eventSubscriber.js +16 -9
  47. package/lib/events/fetchLogs.d.ts +10 -1
  48. package/lib/events/fetchLogs.js +27 -7
  49. package/lib/events/pollingLogProvider.d.ts +2 -1
  50. package/lib/events/pollingLogProvider.js +6 -2
  51. package/lib/events/sort.js +8 -11
  52. package/lib/events/types.d.ts +7 -2
  53. package/lib/events/types.js +5 -0
  54. package/lib/examples/makeTradeExample.js +20 -8
  55. package/lib/factory/bigNum.d.ts +1 -0
  56. package/lib/factory/bigNum.js +34 -10
  57. package/lib/idl/clearing_house.json +4313 -1433
  58. package/lib/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  59. package/lib/index.d.ts +21 -6
  60. package/lib/index.js +25 -6
  61. package/lib/math/amm.d.ts +8 -5
  62. package/lib/math/amm.js +68 -46
  63. package/lib/math/auction.js +4 -1
  64. package/lib/math/conversion.js +1 -1
  65. package/lib/math/funding.d.ts +6 -6
  66. package/lib/math/funding.js +11 -10
  67. package/lib/math/insurance.d.ts +4 -0
  68. package/lib/math/insurance.js +27 -0
  69. package/lib/math/margin.d.ts +11 -0
  70. package/lib/math/margin.js +77 -0
  71. package/lib/math/market.d.ts +14 -9
  72. package/lib/math/market.js +69 -7
  73. package/lib/math/oracles.d.ts +4 -0
  74. package/lib/math/oracles.js +36 -8
  75. package/lib/math/orders.d.ts +6 -2
  76. package/lib/math/orders.js +78 -3
  77. package/lib/math/position.d.ts +21 -13
  78. package/lib/math/position.js +76 -36
  79. package/lib/math/repeg.js +14 -5
  80. package/lib/math/spotBalance.d.ts +22 -0
  81. package/lib/math/spotBalance.js +193 -0
  82. package/lib/math/spotMarket.d.ts +4 -0
  83. package/lib/math/spotMarket.js +8 -0
  84. package/lib/math/spotPosition.d.ts +6 -0
  85. package/lib/math/spotPosition.js +23 -0
  86. package/lib/math/trade.d.ts +10 -10
  87. package/lib/math/trade.js +22 -29
  88. package/lib/oracles/pythClient.js +1 -1
  89. package/lib/oracles/quoteAssetOracleClient.js +1 -1
  90. package/lib/oracles/switchboardClient.js +1 -1
  91. package/lib/orderParams.d.ts +14 -5
  92. package/lib/orderParams.js +12 -92
  93. package/lib/serum/serumSubscriber.d.ts +23 -0
  94. package/lib/serum/serumSubscriber.js +41 -0
  95. package/lib/serum/types.d.ts +11 -0
  96. package/lib/serum/types.js +2 -0
  97. package/lib/slot/SlotSubscriber.d.ts +7 -0
  98. package/lib/slot/SlotSubscriber.js +3 -0
  99. package/lib/{mockUSDCFaucet.d.ts → tokenFaucet.d.ts} +8 -5
  100. package/lib/{mockUSDCFaucet.js → tokenFaucet.js} +63 -51
  101. package/lib/tx/retryTxSender.d.ts +1 -1
  102. package/lib/tx/retryTxSender.js +13 -4
  103. package/lib/tx/types.d.ts +1 -1
  104. package/lib/tx/utils.js +1 -1
  105. package/lib/types.d.ts +474 -123
  106. package/lib/types.js +99 -5
  107. package/lib/userMap/userMap.d.ts +25 -0
  108. package/lib/userMap/userMap.js +73 -0
  109. package/lib/userMap/userStatsMap.d.ts +19 -0
  110. package/lib/userMap/userStatsMap.js +68 -0
  111. package/lib/util/computeUnits.js +1 -1
  112. package/lib/util/getTokenAddress.d.ts +2 -0
  113. package/lib/util/getTokenAddress.js +9 -0
  114. package/package.json +9 -5
  115. package/src/accounts/bulkUserStatsSubscription.ts +33 -0
  116. package/src/accounts/bulkUserSubscription.ts +0 -1
  117. package/src/accounts/fetch.ts +27 -2
  118. package/src/accounts/pollingClearingHouseAccountSubscriber.ts +46 -42
  119. package/src/accounts/pollingUserStatsAccountSubscriber.ts +172 -0
  120. package/src/accounts/types.ts +31 -11
  121. package/src/accounts/webSocketClearingHouseAccountSubscriber.ts +64 -59
  122. package/src/accounts/webSocketUserStatsAccountSubsriber.ts +80 -0
  123. package/src/addresses/marketAddresses.ts +1 -2
  124. package/src/addresses/pda.ts +88 -14
  125. package/src/admin.ts +333 -128
  126. package/src/assert/assert.js +9 -0
  127. package/src/clearingHouse.ts +2464 -458
  128. package/src/clearingHouseConfig.ts +4 -3
  129. package/src/clearingHouseUser.ts +747 -291
  130. package/src/clearingHouseUserStats.ts +75 -0
  131. package/src/clearingHouseUserStatsConfig.ts +18 -0
  132. package/src/config.ts +30 -31
  133. package/src/constants/numericConstants.ts +41 -25
  134. package/src/constants/{markets.ts → perpMarkets.ts} +10 -10
  135. package/src/constants/spotMarkets.ts +72 -0
  136. package/src/dlob/DLOB.ts +868 -0
  137. package/src/dlob/DLOBNode.ts +162 -0
  138. package/src/dlob/NodeList.ts +185 -0
  139. package/src/events/eventList.js +77 -0
  140. package/src/events/eventList.ts +3 -0
  141. package/src/events/eventSubscriber.ts +20 -12
  142. package/src/events/fetchLogs.ts +35 -8
  143. package/src/events/pollingLogProvider.ts +10 -2
  144. package/src/events/sort.ts +11 -15
  145. package/src/events/types.ts +16 -1
  146. package/src/examples/makeTradeExample.js +157 -0
  147. package/src/examples/makeTradeExample.ts +32 -14
  148. package/src/factory/bigNum.ts +42 -13
  149. package/src/idl/clearing_house.json +4313 -1433
  150. package/src/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  151. package/src/index.ts +21 -6
  152. package/src/math/amm.ts +136 -66
  153. package/src/math/auction.ts +5 -1
  154. package/src/math/conversion.ts +2 -2
  155. package/src/math/funding.ts +20 -18
  156. package/src/math/insurance.ts +35 -0
  157. package/src/math/margin.ts +127 -0
  158. package/src/math/market.ts +138 -12
  159. package/src/math/oracles.ts +63 -9
  160. package/src/math/orders.ts +138 -4
  161. package/src/math/position.ts +119 -58
  162. package/src/math/repeg.ts +16 -6
  163. package/src/math/spotBalance.ts +316 -0
  164. package/src/math/spotMarket.ts +9 -0
  165. package/src/math/spotPosition.ts +47 -0
  166. package/src/math/trade.ts +43 -49
  167. package/src/oracles/pythClient.ts +2 -2
  168. package/src/oracles/quoteAssetOracleClient.ts +2 -2
  169. package/src/oracles/switchboardClient.ts +2 -2
  170. package/src/orderParams.ts +24 -137
  171. package/src/serum/serumSubscriber.ts +80 -0
  172. package/src/serum/types.ts +13 -0
  173. package/src/slot/SlotSubscriber.ts +11 -1
  174. package/src/token/index.js +38 -0
  175. package/src/{mockUSDCFaucet.ts → tokenFaucet.ts} +82 -70
  176. package/src/tx/retryTxSender.ts +16 -5
  177. package/src/tx/types.js +2 -0
  178. package/src/tx/types.ts +2 -1
  179. package/src/tx/utils.js +17 -0
  180. package/src/tx/utils.ts +1 -1
  181. package/src/types.ts +477 -125
  182. package/src/userMap/userMap.ts +100 -0
  183. package/src/userMap/userStatsMap.ts +110 -0
  184. package/src/util/computeUnits.js +21 -11
  185. package/src/util/computeUnits.ts +1 -1
  186. package/src/util/getTokenAddress.js +9 -0
  187. package/src/util/getTokenAddress.ts +18 -0
  188. package/src/util/promiseTimeout.js +14 -0
  189. package/src/util/tps.js +27 -0
  190. package/tests/bn/test.ts +12 -3
  191. package/tests/dlob/helpers.ts +374 -0
  192. package/tests/dlob/test.ts +2865 -0
  193. package/lib/constants/banks.d.ts +0 -16
  194. package/lib/constants/banks.js +0 -34
  195. package/lib/constants/markets.d.ts +0 -19
  196. package/lib/math/bankBalance.d.ts +0 -9
  197. package/lib/math/bankBalance.js +0 -75
  198. package/lib/math/state.d.ts +0 -8
  199. package/lib/math/state.js +0 -15
  200. package/lib/orders.d.ts +0 -8
  201. package/lib/orders.js +0 -134
  202. package/src/constants/banks.ts +0 -43
  203. package/src/math/bankBalance.ts +0 -112
  204. package/src/math/state.ts +0 -14
  205. package/src/math/utils.js +0 -27
  206. package/src/math/utils.js.map +0 -1
  207. package/src/orders.ts +0 -244
  208. package/src/util/computeUnits.js.map +0 -1
@@ -0,0 +1,2865 @@
1
+ import { expect } from 'chai';
2
+ import { PublicKey, Keypair } from '@solana/web3.js';
3
+
4
+ import {
5
+ getVariant,
6
+ MarketType,
7
+ DLOB,
8
+ BN,
9
+ BASE_PRECISION,
10
+ PositionDirection,
11
+ OrderStatus,
12
+ OrderType,
13
+ OrderTriggerCondition,
14
+ } from '../../src';
15
+
16
+ import { mockPerpMarkets, mockSpotMarkets } from './helpers';
17
+
18
+ function insertOrderToDLOB(
19
+ dlob: DLOB,
20
+ userAccount: PublicKey,
21
+ orderType: OrderType,
22
+ marketType: MarketType,
23
+ orderId: BN,
24
+ marketIndex: BN,
25
+ price: BN,
26
+ baseAssetAmount: BN,
27
+ direction: PositionDirection,
28
+ auctionStartPrice: BN,
29
+ auctionEndPrice: BN,
30
+ slot?: BN,
31
+ timeInForce = 30
32
+ ) {
33
+ dlob.insertOrder(
34
+ {
35
+ status: OrderStatus.OPEN,
36
+ orderType,
37
+ marketType,
38
+ ts: new BN(getMockTimestamp()),
39
+ slot: slot || new BN(1),
40
+ orderId,
41
+ userOrderId: 0,
42
+ marketIndex,
43
+ price,
44
+ baseAssetAmount,
45
+ baseAssetAmountFilled: new BN(0),
46
+ quoteAssetAmount: new BN(0),
47
+ quoteAssetAmountFilled: new BN(0),
48
+ fee: new BN(0),
49
+ direction,
50
+ reduceOnly: false,
51
+ triggerPrice: new BN(0),
52
+ triggerCondition: OrderTriggerCondition.ABOVE,
53
+ triggered: false,
54
+ existingPositionDirection: PositionDirection.LONG,
55
+ postOnly: false,
56
+ immediateOrCancel: true,
57
+ oraclePriceOffset: new BN(0),
58
+ auctionDuration: 10,
59
+ auctionStartPrice,
60
+ auctionEndPrice,
61
+ timeInForce,
62
+ },
63
+ userAccount
64
+ );
65
+ }
66
+
67
+ function insertTriggerOrderToDLOB(
68
+ dlob: DLOB,
69
+ userAccount: PublicKey,
70
+ orderType: OrderType,
71
+ marketType: MarketType,
72
+ orderId: BN,
73
+ marketIndex: BN,
74
+ price: BN,
75
+ baseAssetAmount: BN,
76
+ direction: PositionDirection,
77
+ triggerPrice: BN,
78
+ triggerCondition: OrderTriggerCondition,
79
+ auctionStartPrice: BN,
80
+ auctionEndPrice: BN,
81
+ slot?: BN,
82
+ timeInForce = 30
83
+ ) {
84
+ dlob.insertOrder(
85
+ {
86
+ status: OrderStatus.OPEN,
87
+ orderType,
88
+ marketType,
89
+ ts: new BN(getMockTimestamp()),
90
+ slot: slot || new BN(1),
91
+ orderId,
92
+ userOrderId: 0,
93
+ marketIndex,
94
+ price,
95
+ baseAssetAmount,
96
+ baseAssetAmountFilled: new BN(0),
97
+ quoteAssetAmount: new BN(0),
98
+ quoteAssetAmountFilled: new BN(0),
99
+ fee: new BN(0),
100
+ direction,
101
+ reduceOnly: false,
102
+ triggerPrice,
103
+ triggerCondition,
104
+ triggered: false,
105
+ existingPositionDirection: PositionDirection.LONG,
106
+ postOnly: false,
107
+ immediateOrCancel: true,
108
+ oraclePriceOffset: new BN(0),
109
+ auctionDuration: 10,
110
+ auctionStartPrice,
111
+ auctionEndPrice,
112
+ timeInForce,
113
+ },
114
+ userAccount
115
+ );
116
+ }
117
+
118
+ let mockTs = 1;
119
+ function getMockTimestamp(): number {
120
+ return mockTs++;
121
+ }
122
+
123
+ describe('DLOB Tests', () => {
124
+ it('Fresh DLOB is empty', () => {
125
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
126
+ const vAsk = new BN(11);
127
+ const vBid = new BN(10);
128
+ const oracle = {
129
+ price: vBid.add(vAsk).div(new BN(2)),
130
+ slot: new BN(12),
131
+ confidence: new BN(1),
132
+ hasSufficientNumberOfDataPoints: true,
133
+ };
134
+
135
+ // check perps
136
+ for (const market of mockPerpMarkets) {
137
+ let foundAsks = 0;
138
+ for (const _ask of dlob.getAsks(
139
+ market.marketIndex,
140
+ vAsk,
141
+ 0,
142
+ MarketType.PERP,
143
+ oracle
144
+ )) {
145
+ foundAsks++;
146
+ }
147
+ expect(foundAsks).to.equal(1);
148
+
149
+ let foundBids = 0;
150
+ for (const _bid of dlob.getBids(
151
+ market.marketIndex,
152
+ vBid,
153
+ 0,
154
+ MarketType.PERP,
155
+ oracle
156
+ )) {
157
+ foundBids++;
158
+ }
159
+ expect(foundBids).to.equal(1);
160
+ }
161
+
162
+ // check spots
163
+ for (const market of mockSpotMarkets) {
164
+ let foundAsks = 0;
165
+ for (const _ask of dlob.getAsks(
166
+ market.marketIndex,
167
+ undefined,
168
+ 0,
169
+ MarketType.SPOT,
170
+ oracle
171
+ )) {
172
+ foundAsks++;
173
+ }
174
+ expect(foundAsks).to.equal(0);
175
+ let foundBids = 0;
176
+ for (const _bid of dlob.getBids(
177
+ market.marketIndex,
178
+ undefined,
179
+ 0,
180
+ MarketType.SPOT,
181
+ oracle
182
+ )) {
183
+ foundBids++;
184
+ }
185
+ expect(foundBids).to.equal(0);
186
+ }
187
+ });
188
+ });
189
+
190
+ describe('DLOB Perp Tests', () => {
191
+ it('Test proper bids', () => {
192
+ const vAsk = new BN(15);
193
+ const vBid = new BN(10);
194
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
195
+ const marketIndex = new BN(0);
196
+
197
+ const slot = 12;
198
+ const oracle = {
199
+ price: vBid.add(vAsk).div(new BN(2)),
200
+ slot: new BN(slot),
201
+ confidence: new BN(1),
202
+ hasSufficientNumberOfDataPoints: true,
203
+ };
204
+ const testCases = [
205
+ {
206
+ expectedIdx: 3,
207
+ isVamm: false,
208
+ orderId: new BN(5),
209
+ price: new BN(0),
210
+ direction: PositionDirection.LONG,
211
+ orderType: OrderType.MARKET,
212
+ },
213
+ {
214
+ expectedIdx: 4,
215
+ isVamm: false,
216
+ orderId: new BN(6),
217
+ price: new BN(0),
218
+ direction: PositionDirection.LONG,
219
+ orderType: OrderType.MARKET,
220
+ },
221
+ {
222
+ expectedIdx: 5,
223
+ isVamm: false,
224
+ orderId: new BN(7),
225
+ price: new BN(0),
226
+ direction: PositionDirection.LONG,
227
+ orderType: OrderType.MARKET,
228
+ },
229
+ {
230
+ expectedIdx: 0,
231
+ isVamm: false,
232
+ orderId: new BN(1),
233
+ price: new BN(12),
234
+ direction: PositionDirection.LONG,
235
+ orderType: OrderType.LIMIT,
236
+ },
237
+ {
238
+ expectedIdx: 1,
239
+ isVamm: false,
240
+ orderId: new BN(2),
241
+ price: new BN(11),
242
+ direction: PositionDirection.LONG,
243
+ orderType: OrderType.LIMIT,
244
+ },
245
+ {
246
+ expectedIdx: 7,
247
+ isVamm: false,
248
+ orderId: new BN(3),
249
+ price: new BN(8),
250
+ direction: PositionDirection.LONG,
251
+ orderType: OrderType.LIMIT,
252
+ },
253
+ {
254
+ expectedIdx: 2,
255
+ isVamm: true,
256
+ orderId: undefined,
257
+ price: undefined,
258
+ direction: undefined,
259
+ orderType: undefined,
260
+ },
261
+ {
262
+ expectedIdx: 6,
263
+ isVamm: false,
264
+ orderId: new BN(4),
265
+ price: new BN(9),
266
+ direction: PositionDirection.LONG,
267
+ orderType: OrderType.LIMIT,
268
+ },
269
+ ];
270
+
271
+ for (const t of testCases) {
272
+ if (t.isVamm) {
273
+ continue;
274
+ }
275
+ insertOrderToDLOB(
276
+ dlob,
277
+ Keypair.generate().publicKey,
278
+ t.orderType || OrderType.LIMIT,
279
+ MarketType.PERP,
280
+ t.orderId || new BN(0), // orderId
281
+ marketIndex,
282
+ t.price || new BN(0), // price
283
+ BASE_PRECISION, // quantity
284
+ t.direction || PositionDirection.LONG,
285
+ vBid,
286
+ vAsk
287
+ );
288
+ }
289
+
290
+ const expectedTestCase = testCases.sort((a, b) => {
291
+ // ascending order
292
+ return a.expectedIdx - b.expectedIdx;
293
+ });
294
+ const bids = dlob.getBids(marketIndex, vBid, slot, MarketType.PERP, oracle);
295
+
296
+ console.log('The Book Bids:');
297
+ let countBids = 0;
298
+ for (const bid of bids) {
299
+ console.log(
300
+ ` . vAMMNode? ${bid.isVammNode()}, ${JSON.stringify(
301
+ bid.order?.orderType
302
+ )} , ${bid.order?.orderId.toString()} , vammTestgetPRice: ${bid.getPrice(
303
+ oracle,
304
+ slot
305
+ )}, price: ${bid.order?.price.toString()}, quantity: ${bid.order?.baseAssetAmountFilled.toString()}/${bid.order?.baseAssetAmount.toString()}`
306
+ );
307
+
308
+ expect(bid.isVammNode()).to.be.eq(expectedTestCase[countBids].isVamm);
309
+ expect(bid.order?.orderId.toNumber()).to.equal(
310
+ expectedTestCase[countBids].orderId?.toNumber()
311
+ );
312
+ expect(bid.order?.price.toNumber()).to.equal(
313
+ expectedTestCase[countBids].price?.toNumber()
314
+ );
315
+ expect(bid.order?.direction).to.equal(
316
+ expectedTestCase[countBids].direction
317
+ );
318
+ expect(bid.order?.orderType).to.equal(
319
+ expectedTestCase[countBids].orderType
320
+ );
321
+ countBids++;
322
+ }
323
+
324
+ expect(countBids).to.equal(testCases.length);
325
+ });
326
+
327
+ it('Test proper bids on multiple markets', () => {
328
+ const vAsk = new BN(15);
329
+ const vBid = new BN(10);
330
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
331
+ const marketIndex0 = new BN(0);
332
+ const marketIndex1 = new BN(1);
333
+
334
+ const slot = 12;
335
+ const oracle = {
336
+ price: vBid.add(vAsk).div(new BN(2)),
337
+ slot: new BN(slot),
338
+ confidence: new BN(1),
339
+ hasSufficientNumberOfDataPoints: true,
340
+ };
341
+ const testCases = [
342
+ {
343
+ expectedIdx: 3,
344
+ isVamm: false,
345
+ orderId: new BN(5),
346
+ price: new BN(0),
347
+ direction: PositionDirection.LONG,
348
+ orderType: OrderType.MARKET,
349
+ marketIndex: marketIndex0,
350
+ },
351
+ {
352
+ expectedIdx: 4,
353
+ isVamm: false,
354
+ orderId: new BN(6),
355
+ price: new BN(0),
356
+ direction: PositionDirection.LONG,
357
+ orderType: OrderType.MARKET,
358
+ marketIndex: marketIndex0,
359
+ },
360
+ {
361
+ expectedIdx: 5,
362
+ isVamm: false,
363
+ orderId: new BN(7),
364
+ price: new BN(0),
365
+ direction: PositionDirection.LONG,
366
+ orderType: OrderType.MARKET,
367
+ marketIndex: marketIndex1,
368
+ },
369
+ {
370
+ expectedIdx: 0,
371
+ isVamm: false,
372
+ orderId: new BN(1),
373
+ price: new BN(12),
374
+ direction: PositionDirection.LONG,
375
+ orderType: OrderType.LIMIT,
376
+ marketIndex: marketIndex0,
377
+ },
378
+ {
379
+ expectedIdx: 1,
380
+ isVamm: false,
381
+ orderId: new BN(2),
382
+ price: new BN(11),
383
+ direction: PositionDirection.LONG,
384
+ orderType: OrderType.LIMIT,
385
+ marketIndex: marketIndex0,
386
+ },
387
+ {
388
+ expectedIdx: 7,
389
+ isVamm: false,
390
+ orderId: new BN(3),
391
+ price: new BN(8),
392
+ direction: PositionDirection.LONG,
393
+ orderType: OrderType.LIMIT,
394
+ marketIndex: marketIndex0,
395
+ },
396
+ {
397
+ expectedIdx: 6,
398
+ isVamm: false,
399
+ orderId: new BN(4),
400
+ price: new BN(9),
401
+ direction: PositionDirection.LONG,
402
+ orderType: OrderType.LIMIT,
403
+ marketIndex: marketIndex1,
404
+ },
405
+ ];
406
+
407
+ for (const t of testCases) {
408
+ if (t.isVamm) {
409
+ continue;
410
+ }
411
+ insertOrderToDLOB(
412
+ dlob,
413
+ Keypair.generate().publicKey,
414
+ t.orderType || OrderType.LIMIT,
415
+ MarketType.PERP,
416
+ t.orderId || new BN(0), // orderId
417
+ t.marketIndex,
418
+ t.price || new BN(0), // price
419
+ BASE_PRECISION, // quantity
420
+ t.direction || PositionDirection.LONG,
421
+ vBid,
422
+ vAsk
423
+ );
424
+ }
425
+
426
+ const bids0 = dlob.getBids(
427
+ marketIndex0,
428
+ vBid,
429
+ slot,
430
+ MarketType.PERP,
431
+ oracle
432
+ );
433
+ let countBids0 = 0;
434
+ for (const bid of bids0) {
435
+ console.log(
436
+ ` . vAMMNode? ${bid.isVammNode()}, ${JSON.stringify(
437
+ bid.order?.orderType
438
+ )} , ${bid.order?.orderId.toString()} , vammTestgetPRice: ${bid.getPrice(
439
+ oracle,
440
+ slot
441
+ )}, price: ${bid.order?.price.toString()}, quantity: ${bid.order?.baseAssetAmountFilled.toString()}/${bid.order?.baseAssetAmount.toString()}`
442
+ );
443
+ countBids0++;
444
+ }
445
+ expect(countBids0).to.equal(6);
446
+
447
+ const bids1 = dlob.getBids(
448
+ marketIndex1,
449
+ vBid,
450
+ slot,
451
+ MarketType.PERP,
452
+ oracle
453
+ );
454
+ let countBids1 = 0;
455
+ for (const bid of bids1) {
456
+ console.log(
457
+ ` . vAMMNode? ${bid.isVammNode()}, ${JSON.stringify(
458
+ bid.order?.orderType
459
+ )} , ${bid.order?.orderId.toString()} , vammTestgetPRice: ${bid.getPrice(
460
+ oracle,
461
+ slot
462
+ )}, price: ${bid.order?.price.toString()}, quantity: ${bid.order?.baseAssetAmountFilled.toString()}/${bid.order?.baseAssetAmount.toString()}`
463
+ );
464
+
465
+ countBids1++;
466
+ }
467
+ expect(countBids1).to.equal(3);
468
+ });
469
+
470
+ it('Test proper asks', () => {
471
+ const vAsk = new BN(15);
472
+ const vBid = new BN(10);
473
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
474
+ const marketIndex = new BN(0);
475
+
476
+ const slot = 12;
477
+ const oracle = {
478
+ price: vBid.add(vAsk).div(new BN(2)),
479
+ slot: new BN(slot),
480
+ confidence: new BN(1),
481
+ hasSufficientNumberOfDataPoints: true,
482
+ };
483
+ const testCases = [
484
+ {
485
+ expectedIdx: 0,
486
+ isVamm: false,
487
+ orderId: new BN(3),
488
+ price: new BN(0),
489
+ direction: PositionDirection.SHORT,
490
+ orderType: OrderType.MARKET,
491
+ },
492
+ {
493
+ expectedIdx: 1,
494
+ isVamm: false,
495
+ orderId: new BN(4),
496
+ price: new BN(0),
497
+ direction: PositionDirection.SHORT,
498
+ orderType: OrderType.MARKET,
499
+ },
500
+ {
501
+ expectedIdx: 2,
502
+ isVamm: false,
503
+ orderId: new BN(5),
504
+ price: new BN(0),
505
+ direction: PositionDirection.SHORT,
506
+ orderType: OrderType.MARKET,
507
+ },
508
+ {
509
+ expectedIdx: 3,
510
+ isVamm: false,
511
+ orderId: new BN(1),
512
+ price: new BN(13),
513
+ direction: PositionDirection.SHORT,
514
+ orderType: OrderType.LIMIT,
515
+ },
516
+ {
517
+ expectedIdx: 6,
518
+ isVamm: false,
519
+ orderId: new BN(6),
520
+ price: new BN(16),
521
+ direction: PositionDirection.SHORT,
522
+ orderType: OrderType.LIMIT,
523
+ },
524
+ {
525
+ expectedIdx: 5,
526
+ isVamm: true,
527
+ orderId: undefined,
528
+ price: undefined,
529
+ direction: undefined,
530
+ orderType: undefined,
531
+ },
532
+ {
533
+ expectedIdx: 7,
534
+ isVamm: false,
535
+ orderId: new BN(7),
536
+ price: new BN(17),
537
+ direction: PositionDirection.SHORT,
538
+ orderType: OrderType.LIMIT,
539
+ },
540
+ {
541
+ expectedIdx: 4,
542
+ isVamm: false,
543
+ orderId: new BN(2),
544
+ price: new BN(14),
545
+ direction: PositionDirection.SHORT,
546
+ orderType: OrderType.LIMIT,
547
+ },
548
+ ];
549
+
550
+ for (const t of testCases) {
551
+ if (t.isVamm) {
552
+ continue;
553
+ }
554
+ insertOrderToDLOB(
555
+ dlob,
556
+ Keypair.generate().publicKey,
557
+ t.orderType || OrderType.LIMIT,
558
+ MarketType.PERP,
559
+ t.orderId || new BN(0), // orderId
560
+ marketIndex,
561
+ t.price || new BN(0), // price
562
+ BASE_PRECISION, // quantity
563
+ t.direction || PositionDirection.SHORT,
564
+ vBid,
565
+ vAsk
566
+ );
567
+ }
568
+
569
+ const expectedTestCase = testCases.sort((a, b) => {
570
+ // ascending order
571
+ return a.expectedIdx - b.expectedIdx;
572
+ });
573
+ const asks = dlob.getAsks(marketIndex, vAsk, slot, MarketType.PERP, oracle);
574
+
575
+ console.log('The Book Asks:');
576
+ let countAsks = 0;
577
+ for (const ask of asks) {
578
+ console.log(
579
+ ` . vAMMNode? ${ask.isVammNode()}, ${JSON.stringify(
580
+ ask.order?.orderType
581
+ )} , ${ask.order?.orderId.toString()} , vammTestgetPRice: ${ask.getPrice(
582
+ oracle,
583
+ slot
584
+ )}, price: ${ask.order?.price.toString()}, quantity: ${ask.order?.baseAssetAmountFilled.toString()}/${ask.order?.baseAssetAmount.toString()}`
585
+ );
586
+
587
+ expect(ask.isVammNode()).to.be.eq(expectedTestCase[countAsks].isVamm);
588
+ expect(ask.order?.orderId.toNumber()).to.equal(
589
+ expectedTestCase[countAsks].orderId?.toNumber()
590
+ );
591
+ expect(ask.order?.price.toNumber()).to.equal(
592
+ expectedTestCase[countAsks].price?.toNumber()
593
+ );
594
+ expect(ask.order?.direction).to.equal(
595
+ expectedTestCase[countAsks].direction
596
+ );
597
+ expect(ask.order?.orderType).to.equal(
598
+ expectedTestCase[countAsks].orderType
599
+ );
600
+ countAsks++;
601
+ }
602
+
603
+ expect(countAsks).to.equal(testCases.length);
604
+ });
605
+
606
+ it('Test insert market orders', () => {
607
+ const vAsk = new BN(11);
608
+ const vBid = new BN(10);
609
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
610
+ const marketIndex = new BN(0);
611
+ const oracle = {
612
+ price: vBid.add(vAsk).div(new BN(2)),
613
+ slot: new BN(12),
614
+ confidence: new BN(1),
615
+ hasSufficientNumberOfDataPoints: true,
616
+ };
617
+
618
+ // 3 mkt buys
619
+ for (let i = 0; i < 3; i++) {
620
+ insertOrderToDLOB(
621
+ dlob,
622
+ Keypair.generate().publicKey,
623
+ OrderType.MARKET,
624
+ MarketType.PERP,
625
+ new BN(i + 1),
626
+ marketIndex,
627
+ new BN(0),
628
+ BASE_PRECISION,
629
+ PositionDirection.LONG,
630
+ vBid,
631
+ vAsk
632
+ );
633
+ }
634
+
635
+ // 3 mkt sells
636
+ for (let i = 0; i < 3; i++) {
637
+ insertOrderToDLOB(
638
+ dlob,
639
+ Keypair.generate().publicKey,
640
+ OrderType.MARKET,
641
+ MarketType.PERP,
642
+ new BN(i + 1),
643
+ marketIndex,
644
+ new BN(0),
645
+ BASE_PRECISION,
646
+ PositionDirection.SHORT,
647
+ vBid,
648
+ vAsk
649
+ );
650
+ }
651
+
652
+ let asks = 0;
653
+ for (const ask of dlob.getAsks(
654
+ marketIndex,
655
+ vAsk,
656
+ 2,
657
+ MarketType.PERP,
658
+ oracle
659
+ )) {
660
+ // vamm node is last in asks
661
+ asks++;
662
+
663
+ if (ask.order) {
664
+ // market orders
665
+ expect(getVariant(ask.order?.status)).to.equal('open');
666
+ expect(getVariant(ask.order?.orderType)).to.equal('market');
667
+ expect(getVariant(ask.order?.direction)).to.equal('short');
668
+ expect(ask.order?.orderId.toNumber()).to.equal(asks);
669
+ }
670
+ }
671
+ expect(asks).to.equal(4); // vamm ask + 3 orders
672
+
673
+ let bids = 0;
674
+ for (const bid of dlob.getBids(
675
+ marketIndex,
676
+ vBid,
677
+ 2,
678
+ MarketType.PERP,
679
+ oracle
680
+ )) {
681
+ if (bids === 0) {
682
+ // vamm node
683
+ expect(bid.order).to.equal(undefined);
684
+ } else {
685
+ // market orders
686
+ expect(getVariant(bid.order?.status)).to.equal('open');
687
+ expect(getVariant(bid.order?.orderType)).to.equal('market');
688
+ expect(getVariant(bid.order?.direction)).to.equal('long');
689
+ expect(bid.order?.orderId.toNumber()).to.equal(bids);
690
+ }
691
+ bids++;
692
+ }
693
+ expect(bids).to.equal(4); // vamm bid + 3 orders
694
+ });
695
+
696
+ it('Test insert limit orders', () => {
697
+ const slot = 12;
698
+ const vAsk = new BN(11);
699
+ const vBid = new BN(10);
700
+ const oracle = {
701
+ price: vBid.add(vAsk).div(new BN(2)),
702
+ slot: new BN(slot),
703
+ confidence: new BN(1),
704
+ hasSufficientNumberOfDataPoints: true,
705
+ };
706
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
707
+ const marketIndex = new BN(0);
708
+ insertOrderToDLOB(
709
+ dlob,
710
+ Keypair.generate().publicKey,
711
+ OrderType.LIMIT,
712
+ MarketType.PERP,
713
+ new BN(3),
714
+ marketIndex,
715
+ new BN(5),
716
+ BASE_PRECISION,
717
+ PositionDirection.LONG,
718
+ vBid,
719
+ vAsk
720
+ );
721
+
722
+ insertOrderToDLOB(
723
+ dlob,
724
+ Keypair.generate().publicKey,
725
+ OrderType.LIMIT,
726
+ MarketType.PERP,
727
+ new BN(2),
728
+ marketIndex,
729
+ new BN(6),
730
+ BASE_PRECISION,
731
+ PositionDirection.LONG,
732
+ vBid,
733
+ vAsk
734
+ );
735
+
736
+ insertOrderToDLOB(
737
+ dlob,
738
+ Keypair.generate().publicKey,
739
+ OrderType.LIMIT,
740
+ MarketType.PERP,
741
+ new BN(1),
742
+ marketIndex,
743
+ new BN(7),
744
+ BASE_PRECISION,
745
+ PositionDirection.LONG,
746
+ vBid,
747
+ vAsk
748
+ );
749
+
750
+ insertOrderToDLOB(
751
+ dlob,
752
+ Keypair.generate().publicKey,
753
+ OrderType.LIMIT,
754
+ MarketType.PERP,
755
+ new BN(1),
756
+ marketIndex,
757
+ new BN(12),
758
+ BASE_PRECISION,
759
+ PositionDirection.SHORT,
760
+ vBid,
761
+ vAsk
762
+ );
763
+
764
+ insertOrderToDLOB(
765
+ dlob,
766
+ Keypair.generate().publicKey,
767
+ OrderType.LIMIT,
768
+ MarketType.PERP,
769
+ new BN(2),
770
+ marketIndex,
771
+ new BN(13),
772
+ BASE_PRECISION,
773
+ PositionDirection.SHORT,
774
+ vBid,
775
+ vAsk
776
+ );
777
+
778
+ insertOrderToDLOB(
779
+ dlob,
780
+ Keypair.generate().publicKey,
781
+ OrderType.LIMIT,
782
+ MarketType.PERP,
783
+ new BN(3),
784
+ marketIndex,
785
+ new BN(14),
786
+ BASE_PRECISION,
787
+ PositionDirection.SHORT,
788
+ vBid,
789
+ vAsk
790
+ );
791
+
792
+ let asks = 0;
793
+ for (const ask of dlob.getAsks(
794
+ marketIndex,
795
+ vAsk,
796
+ 2,
797
+ MarketType.PERP,
798
+ oracle
799
+ )) {
800
+ if (ask.order) {
801
+ // market orders
802
+ console.log(`ask price: ${ask.order.price.toString()}`);
803
+ expect(getVariant(ask.order?.status)).to.equal('open');
804
+ expect(getVariant(ask.order?.orderType)).to.equal('limit');
805
+ expect(getVariant(ask.order?.direction)).to.equal('short');
806
+ expect(ask.order?.orderId.toNumber()).to.equal(asks);
807
+ expect(ask.order?.price.gt(vAsk)).to.equal(true);
808
+ }
809
+
810
+ // vamm node is first for limit asks
811
+ asks++;
812
+ }
813
+ expect(asks).to.equal(4); // vamm ask + 3 orders
814
+
815
+ let bids = 0;
816
+ for (const bid of dlob.getBids(
817
+ marketIndex,
818
+ vBid,
819
+ 2,
820
+ MarketType.PERP,
821
+ oracle
822
+ )) {
823
+ if (bids === 0) {
824
+ // vamm node
825
+ expect(bid.order).to.equal(undefined);
826
+ } else {
827
+ // market orders
828
+ console.log(`bid price: ${bid.order?.price.toString()}`);
829
+ expect(getVariant(bid.order?.status)).to.equal('open');
830
+ expect(getVariant(bid.order?.orderType)).to.equal('limit');
831
+ expect(getVariant(bid.order?.direction)).to.equal('long');
832
+ expect(bid.order?.orderId.toNumber()).to.equal(bids);
833
+ expect(bid.order?.price.lt(vBid)).to.equal(true);
834
+ }
835
+ bids++;
836
+ }
837
+ expect(bids).to.equal(4); // vamm bid + 3 orders
838
+ });
839
+
840
+ it('Test multiple market orders fill with multiple limit orders', () => {
841
+ const vAsk = new BN(15);
842
+ const vBid = new BN(10);
843
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
844
+ const marketIndex = new BN(0);
845
+
846
+ // insert some limit buys above vamm bid, below ask
847
+ insertOrderToDLOB(
848
+ dlob,
849
+ Keypair.generate().publicKey,
850
+ OrderType.LIMIT,
851
+ MarketType.PERP,
852
+ new BN(1), // orderId
853
+ marketIndex,
854
+ new BN(11), // price
855
+ BASE_PRECISION, // quantity
856
+ PositionDirection.LONG,
857
+ vBid,
858
+ vAsk
859
+ );
860
+ insertOrderToDLOB(
861
+ dlob,
862
+ Keypair.generate().publicKey,
863
+ OrderType.LIMIT,
864
+ MarketType.PERP,
865
+ new BN(2), // orderId
866
+ marketIndex,
867
+ new BN(12), // price
868
+ BASE_PRECISION, // quantity
869
+ PositionDirection.LONG,
870
+ vBid,
871
+ vAsk
872
+ );
873
+ insertOrderToDLOB(
874
+ dlob,
875
+ Keypair.generate().publicKey,
876
+ OrderType.LIMIT,
877
+ MarketType.PERP,
878
+ new BN(3), // orderId
879
+ marketIndex,
880
+ new BN(13), // price
881
+ BASE_PRECISION, // quantity
882
+ PositionDirection.LONG,
883
+ vBid,
884
+ vAsk
885
+ );
886
+
887
+ // should have no crossing orders
888
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
889
+ marketIndex,
890
+ vBid,
891
+ vAsk,
892
+ 12, // auction over
893
+ MarketType.PERP,
894
+ {
895
+ price: vBid.add(vAsk).div(new BN(2)),
896
+ slot: new BN(12),
897
+ confidence: new BN(1),
898
+ hasSufficientNumberOfDataPoints: true,
899
+ }
900
+ );
901
+ expect(nodesToFillBefore.length).to.equal(0);
902
+
903
+ // place two market sell order eating 2 of the limit orders
904
+ insertOrderToDLOB(
905
+ dlob,
906
+ Keypair.generate().publicKey,
907
+ OrderType.MARKET,
908
+ MarketType.PERP,
909
+ new BN(4), // orderId
910
+ marketIndex,
911
+ new BN(12), // price
912
+ new BN(1).mul(BASE_PRECISION), // quantity
913
+ PositionDirection.SHORT,
914
+ vBid,
915
+ vAsk
916
+ );
917
+ insertOrderToDLOB(
918
+ dlob,
919
+ Keypair.generate().publicKey,
920
+ OrderType.MARKET,
921
+ MarketType.PERP,
922
+ new BN(5), // orderId
923
+ marketIndex,
924
+ new BN(12), // price
925
+ new BN(1).mul(BASE_PRECISION), // quantity
926
+ PositionDirection.SHORT,
927
+ vBid,
928
+ vAsk
929
+ );
930
+
931
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
932
+ marketIndex,
933
+ vBid,
934
+ vAsk,
935
+ 12, // auction over
936
+ MarketType.PERP,
937
+ {
938
+ price: vBid.add(vAsk).div(new BN(2)),
939
+ slot: new BN(12),
940
+ confidence: new BN(1),
941
+ hasSufficientNumberOfDataPoints: true,
942
+ }
943
+ );
944
+ for (const n of nodesToFillAfter) {
945
+ console.log(
946
+ `cross found: taker orderId: ${n.node.order?.orderId.toString()}: BAA: ${n.node.order?.baseAssetAmount.toString()}, maker orderId: ${n.makerNode?.order?.orderId.toString()}: BAA: ${n.makerNode?.order?.baseAssetAmount.toString()}`
947
+ );
948
+ }
949
+ expect(nodesToFillAfter.length).to.equal(2);
950
+
951
+ // first taker should fill with best maker
952
+ expect(nodesToFillAfter[0].node.order?.orderId.toNumber()).to.equal(4);
953
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId.toNumber()).to.equal(
954
+ 3
955
+ );
956
+
957
+ // second taker should fill with second best maker
958
+ expect(nodesToFillAfter[1].node.order?.orderId.toNumber()).to.equal(5);
959
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId.toNumber()).to.equal(
960
+ 2
961
+ );
962
+ });
963
+
964
+ it('Test one market orders fills two limit orders', () => {
965
+ const vAsk = new BN(15);
966
+ const vBid = new BN(10);
967
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
968
+ const marketIndex = new BN(0);
969
+
970
+ // insert some limit sells below vAMM ask, above bid
971
+ insertOrderToDLOB(
972
+ dlob,
973
+ Keypair.generate().publicKey,
974
+ OrderType.LIMIT,
975
+ MarketType.PERP,
976
+ new BN(1), // orderId
977
+ marketIndex,
978
+ new BN(14), // price
979
+ BASE_PRECISION, // quantity
980
+ PositionDirection.SHORT,
981
+ vBid,
982
+ vAsk
983
+ );
984
+ insertOrderToDLOB(
985
+ dlob,
986
+ Keypair.generate().publicKey,
987
+ OrderType.LIMIT,
988
+ MarketType.PERP,
989
+ new BN(2), // orderId
990
+ marketIndex,
991
+ new BN(13), // price
992
+ BASE_PRECISION, // quantity
993
+ PositionDirection.SHORT,
994
+ vBid,
995
+ vAsk
996
+ );
997
+ insertOrderToDLOB(
998
+ dlob,
999
+ Keypair.generate().publicKey,
1000
+ OrderType.LIMIT,
1001
+ MarketType.PERP,
1002
+ new BN(3), // orderId
1003
+ marketIndex,
1004
+ new BN(12), // price
1005
+ BASE_PRECISION, // quantity
1006
+ PositionDirection.SHORT,
1007
+ vBid,
1008
+ vAsk
1009
+ );
1010
+
1011
+ // should have no crossing orders
1012
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
1013
+ marketIndex,
1014
+ vBid,
1015
+ vAsk,
1016
+ 12, // auction over
1017
+ MarketType.PERP,
1018
+ {
1019
+ price: vBid.add(vAsk).div(new BN(2)),
1020
+ slot: new BN(12),
1021
+ confidence: new BN(1),
1022
+ hasSufficientNumberOfDataPoints: true,
1023
+ }
1024
+ );
1025
+ expect(nodesToFillBefore.length).to.equal(0);
1026
+
1027
+ // place one market buy order eating 2 of the limit orders
1028
+ insertOrderToDLOB(
1029
+ dlob,
1030
+ Keypair.generate().publicKey,
1031
+ OrderType.MARKET,
1032
+ MarketType.PERP,
1033
+ new BN(4), // orderId
1034
+ marketIndex,
1035
+ new BN(13), // price
1036
+ new BN(2).mul(BASE_PRECISION), // quantity
1037
+ PositionDirection.LONG,
1038
+ vBid,
1039
+ vAsk
1040
+ );
1041
+
1042
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
1043
+ marketIndex,
1044
+ vBid,
1045
+ vAsk,
1046
+ 12, // auction over
1047
+ MarketType.PERP,
1048
+ {
1049
+ price: vBid.add(vAsk).div(new BN(2)),
1050
+ slot: new BN(12),
1051
+ confidence: new BN(1),
1052
+ hasSufficientNumberOfDataPoints: true,
1053
+ }
1054
+ );
1055
+ for (const n of nodesToFillAfter) {
1056
+ console.log(
1057
+ `cross found: taker orderId: ${n.node.order?.orderId.toString()}: BAA: ${n.node.order?.baseAssetAmountFilled.toString()}/${n.node.order?.baseAssetAmount.toString()}, maker orderId: ${n.makerNode?.order?.orderId.toString()}: BAA: ${n.makerNode?.order?.baseAssetAmountFilled.toString()}/${n.makerNode?.order?.baseAssetAmount.toString()}`
1058
+ );
1059
+ }
1060
+ expect(nodesToFillAfter.length).to.equal(2);
1061
+
1062
+ // taker should fill completely with best maker
1063
+ expect(nodesToFillAfter[0].node.order?.orderId.toNumber()).to.equal(4);
1064
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId.toNumber()).to.equal(
1065
+ 3
1066
+ );
1067
+
1068
+ // taker should fill completely with second best maker
1069
+ expect(nodesToFillAfter[1].node.order?.orderId.toNumber()).to.equal(4);
1070
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId.toNumber()).to.equal(
1071
+ 2
1072
+ );
1073
+ });
1074
+
1075
+ it('Test two market orders to fill one limit order', () => {
1076
+ const vAsk = new BN(15);
1077
+ const vBid = new BN(8);
1078
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
1079
+ const marketIndex = new BN(0);
1080
+
1081
+ const slot = 12;
1082
+ const oracle = {
1083
+ price: vBid.add(vAsk).div(new BN(2)),
1084
+ slot: new BN(slot),
1085
+ confidence: new BN(1),
1086
+ hasSufficientNumberOfDataPoints: true,
1087
+ };
1088
+
1089
+ // insert some limit sells below vAMM ask, above bid
1090
+ insertOrderToDLOB(
1091
+ dlob,
1092
+ Keypair.generate().publicKey,
1093
+ OrderType.LIMIT,
1094
+ MarketType.PERP,
1095
+ new BN(1), // orderId
1096
+ marketIndex,
1097
+ new BN(14), // price
1098
+ BASE_PRECISION, // quantity
1099
+ PositionDirection.SHORT,
1100
+ vBid,
1101
+ vAsk
1102
+ );
1103
+ insertOrderToDLOB(
1104
+ dlob,
1105
+ Keypair.generate().publicKey,
1106
+ OrderType.LIMIT,
1107
+ MarketType.PERP,
1108
+ new BN(2), // orderId
1109
+ marketIndex,
1110
+ new BN(13), // price
1111
+ BASE_PRECISION, // quantity
1112
+ PositionDirection.SHORT,
1113
+ vBid,
1114
+ vAsk
1115
+ );
1116
+ insertOrderToDLOB(
1117
+ dlob,
1118
+ Keypair.generate().publicKey,
1119
+ OrderType.LIMIT,
1120
+ MarketType.PERP,
1121
+ new BN(3), // orderId
1122
+ marketIndex,
1123
+ new BN(9), // price <-- best price
1124
+ new BN(3).mul(BASE_PRECISION), // quantity
1125
+ PositionDirection.SHORT,
1126
+ vBid,
1127
+ vAsk
1128
+ );
1129
+
1130
+ // should have no crossing orders
1131
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
1132
+ marketIndex,
1133
+ vBid,
1134
+ vAsk,
1135
+ 12, // auction over
1136
+ MarketType.PERP,
1137
+ oracle
1138
+ );
1139
+ expect(nodesToFillBefore.length).to.equal(0);
1140
+
1141
+ // place two market buy orders to eat the best ask
1142
+ insertOrderToDLOB(
1143
+ dlob,
1144
+ Keypair.generate().publicKey,
1145
+ OrderType.MARKET,
1146
+ MarketType.PERP,
1147
+ new BN(4), // orderId
1148
+ marketIndex,
1149
+ new BN(0), // price
1150
+ new BN(1).mul(BASE_PRECISION), // quantity
1151
+ PositionDirection.LONG,
1152
+ vBid,
1153
+ vAsk
1154
+ );
1155
+ insertOrderToDLOB(
1156
+ dlob,
1157
+ Keypair.generate().publicKey,
1158
+ OrderType.MARKET,
1159
+ MarketType.PERP,
1160
+ new BN(5), // orderId
1161
+ marketIndex,
1162
+ new BN(0), // price
1163
+ new BN(2).mul(BASE_PRECISION), // quantity
1164
+ PositionDirection.LONG,
1165
+ vBid,
1166
+ vAsk
1167
+ );
1168
+
1169
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
1170
+ marketIndex,
1171
+ vBid,
1172
+ vAsk,
1173
+ slot, // auction over
1174
+ MarketType.PERP,
1175
+ oracle
1176
+ );
1177
+ const mktNodes = dlob.findMarketNodesToFill(
1178
+ marketIndex,
1179
+ slot,
1180
+ MarketType.PERP
1181
+ );
1182
+ console.log(`market nodes: ${mktNodes.length}`);
1183
+
1184
+ const askNodes = dlob.getAsks(
1185
+ marketIndex,
1186
+ vAsk,
1187
+ slot,
1188
+ MarketType.PERP,
1189
+ oracle
1190
+ );
1191
+ let aa = 0;
1192
+ for (const b of askNodes) {
1193
+ console.log(
1194
+ ` . ask: ${b.order?.orderId.toString()}: p1: ${b.order?.price.toString()} p2: ${b.getPrice(
1195
+ oracle,
1196
+ slot
1197
+ )}, ${JSON.stringify(
1198
+ b.order?.orderType
1199
+ )} BAA: ${b.order?.baseAssetAmountFilled.toString()}/${b.order?.baseAssetAmount.toString()}`
1200
+ );
1201
+ aa++;
1202
+ }
1203
+ expect(aa).to.equal(4);
1204
+
1205
+ const bidNodes = dlob.getBids(
1206
+ marketIndex,
1207
+ vBid,
1208
+ slot,
1209
+ MarketType.PERP,
1210
+ oracle
1211
+ );
1212
+ let bb = 0;
1213
+ for (const b of bidNodes) {
1214
+ console.log(
1215
+ ` . bid: ${b.order?.orderId.toString()}: p1: ${b.order?.price.toString()} p2: ${b.getPrice(
1216
+ oracle,
1217
+ slot
1218
+ )}, ${JSON.stringify(
1219
+ b.order?.orderType
1220
+ )} BAA: ${b.order?.baseAssetAmountFilled.toString()}/${b.order?.baseAssetAmount.toString()}`
1221
+ );
1222
+ bb++;
1223
+ }
1224
+ expect(bb).to.equal(3);
1225
+
1226
+ console.log(`bids nodes: ${bb}`);
1227
+ for (const n of nodesToFillAfter) {
1228
+ console.log(
1229
+ `cross found: taker orderId: ${n.node.order?.orderId.toString()}: BAA: ${n.node.order?.baseAssetAmountFilled.toString()}/${n.node.order?.baseAssetAmount.toString()}, maker orderId: ${n.makerNode?.order?.orderId.toString()}: BAA: ${n.makerNode?.order?.baseAssetAmountFilled.toString()}/${n.makerNode?.order?.baseAssetAmount.toString()}`
1230
+ );
1231
+ }
1232
+ expect(nodesToFillAfter.length).to.equal(2);
1233
+
1234
+ // taker should fill completely with best maker
1235
+ expect(nodesToFillAfter[0].node.order?.orderId.toNumber()).to.equal(4);
1236
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId.toNumber()).to.equal(
1237
+ 3
1238
+ );
1239
+
1240
+ // taker should fill completely with second best maker
1241
+ expect(nodesToFillAfter[1].node.order?.orderId.toNumber()).to.equal(5);
1242
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId.toNumber()).to.equal(
1243
+ 3
1244
+ );
1245
+ });
1246
+
1247
+ it('Test trigger orders', () => {
1248
+ const vAsk = new BN(15);
1249
+ const vBid = new BN(8);
1250
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
1251
+ const marketIndex = new BN(0);
1252
+
1253
+ const slot = 20;
1254
+ const oracle = {
1255
+ price: vBid.add(vAsk).div(new BN(2)),
1256
+ slot: new BN(slot),
1257
+ confidence: new BN(1),
1258
+ hasSufficientNumberOfDataPoints: true,
1259
+ };
1260
+
1261
+ const orderIdsToTrigger = [1, 2, 3, 4, 5, 6, 7, 8];
1262
+ // const orderIdsToNotTrigger = [9, 10, 11, 12];
1263
+
1264
+ // should trigger limit buy with above condition
1265
+ insertTriggerOrderToDLOB(
1266
+ dlob,
1267
+ Keypair.generate().publicKey,
1268
+ OrderType.TRIGGER_LIMIT,
1269
+ MarketType.PERP,
1270
+ new BN(1), //orderId
1271
+ marketIndex, // marketIndex
1272
+ vBid, // price
1273
+ BASE_PRECISION, // baseAssetAmount: BN,
1274
+ PositionDirection.LONG,
1275
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
1276
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
1277
+ vBid,
1278
+ vAsk
1279
+ );
1280
+ // should trigger limit sell with above condition
1281
+ insertTriggerOrderToDLOB(
1282
+ dlob,
1283
+ Keypair.generate().publicKey,
1284
+ OrderType.TRIGGER_LIMIT,
1285
+ MarketType.PERP,
1286
+ new BN(2), //orderId
1287
+ marketIndex, // marketIndex
1288
+ vBid, // price
1289
+ BASE_PRECISION, // baseAssetAmount: BN,
1290
+ PositionDirection.SHORT,
1291
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
1292
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
1293
+ vBid,
1294
+ vAsk
1295
+ );
1296
+ // should trigger market buy with above condition
1297
+ insertTriggerOrderToDLOB(
1298
+ dlob,
1299
+ Keypair.generate().publicKey,
1300
+ OrderType.TRIGGER_MARKET,
1301
+ MarketType.PERP,
1302
+ new BN(3), //orderId
1303
+ marketIndex, // marketIndex
1304
+ vAsk, // price
1305
+ BASE_PRECISION, // baseAssetAmount: BN,
1306
+ PositionDirection.LONG,
1307
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
1308
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
1309
+ vBid,
1310
+ vAsk
1311
+ );
1312
+ // should trigger market sell with above condition
1313
+ insertTriggerOrderToDLOB(
1314
+ dlob,
1315
+ Keypair.generate().publicKey,
1316
+ OrderType.TRIGGER_MARKET,
1317
+ MarketType.PERP,
1318
+ new BN(4), //orderId
1319
+ marketIndex, // marketIndex
1320
+ vBid, // price
1321
+ BASE_PRECISION, // baseAssetAmount: BN,
1322
+ PositionDirection.SHORT,
1323
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
1324
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
1325
+ vBid,
1326
+ vAsk
1327
+ );
1328
+ // should trigger limit buy with below condition
1329
+ insertTriggerOrderToDLOB(
1330
+ dlob,
1331
+ Keypair.generate().publicKey,
1332
+ OrderType.TRIGGER_LIMIT,
1333
+ MarketType.PERP,
1334
+ new BN(5), //orderId
1335
+ marketIndex, // marketIndex
1336
+ vBid, // price
1337
+ BASE_PRECISION, // baseAssetAmount: BN,
1338
+ PositionDirection.LONG,
1339
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
1340
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
1341
+ vBid,
1342
+ vAsk
1343
+ );
1344
+ // should trigger limit sell with below condition
1345
+ insertTriggerOrderToDLOB(
1346
+ dlob,
1347
+ Keypair.generate().publicKey,
1348
+ OrderType.TRIGGER_LIMIT,
1349
+ MarketType.PERP,
1350
+ new BN(6), //orderId
1351
+ marketIndex, // marketIndex
1352
+ vBid, // price
1353
+ BASE_PRECISION, // baseAssetAmount: BN,
1354
+ PositionDirection.SHORT,
1355
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
1356
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
1357
+ vBid,
1358
+ vAsk
1359
+ );
1360
+ // should trigger market buy with below condition
1361
+ insertTriggerOrderToDLOB(
1362
+ dlob,
1363
+ Keypair.generate().publicKey,
1364
+ OrderType.TRIGGER_MARKET,
1365
+ MarketType.PERP,
1366
+ new BN(7), //orderId
1367
+ marketIndex, // marketIndex
1368
+ vBid, // price
1369
+ BASE_PRECISION, // baseAssetAmount: BN,
1370
+ PositionDirection.LONG,
1371
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
1372
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
1373
+ vBid,
1374
+ vAsk
1375
+ );
1376
+ // should trigger market sell with below condition
1377
+ insertTriggerOrderToDLOB(
1378
+ dlob,
1379
+ Keypair.generate().publicKey,
1380
+ OrderType.TRIGGER_MARKET,
1381
+ MarketType.PERP,
1382
+ new BN(8), //orderId
1383
+ marketIndex, // marketIndex
1384
+ vBid, // price
1385
+ BASE_PRECISION, // baseAssetAmount: BN,
1386
+ PositionDirection.LONG,
1387
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
1388
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
1389
+ vBid,
1390
+ vAsk
1391
+ );
1392
+
1393
+ // should NOT trigger market sell with above condition
1394
+ insertTriggerOrderToDLOB(
1395
+ dlob,
1396
+ Keypair.generate().publicKey,
1397
+ OrderType.TRIGGER_MARKET,
1398
+ MarketType.PERP,
1399
+ new BN(9), //orderId
1400
+ marketIndex, // marketIndex
1401
+ vBid, // price
1402
+ BASE_PRECISION, // baseAssetAmount: BN,
1403
+ PositionDirection.SHORT,
1404
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
1405
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
1406
+ vBid,
1407
+ vAsk
1408
+ );
1409
+ // should NOT trigger market sell with below condition
1410
+ insertTriggerOrderToDLOB(
1411
+ dlob,
1412
+ Keypair.generate().publicKey,
1413
+ OrderType.TRIGGER_MARKET,
1414
+ MarketType.PERP,
1415
+ new BN(10), //orderId
1416
+ marketIndex, // marketIndex
1417
+ vBid, // price
1418
+ BASE_PRECISION, // baseAssetAmount: BN,
1419
+ PositionDirection.SHORT,
1420
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
1421
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
1422
+ vBid,
1423
+ vAsk
1424
+ );
1425
+ // should NOT trigger market buy with above condition
1426
+ insertTriggerOrderToDLOB(
1427
+ dlob,
1428
+ Keypair.generate().publicKey,
1429
+ OrderType.TRIGGER_MARKET,
1430
+ MarketType.PERP,
1431
+ new BN(11), //orderId
1432
+ marketIndex, // marketIndex
1433
+ vBid, // price
1434
+ BASE_PRECISION, // baseAssetAmount: BN,
1435
+ PositionDirection.LONG,
1436
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
1437
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
1438
+ vBid,
1439
+ vAsk
1440
+ );
1441
+ // should NOT trigger market buy with below condition
1442
+ insertTriggerOrderToDLOB(
1443
+ dlob,
1444
+ Keypair.generate().publicKey,
1445
+ OrderType.TRIGGER_MARKET,
1446
+ MarketType.PERP,
1447
+ new BN(12), //orderId
1448
+ marketIndex, // marketIndex
1449
+ vBid, // price
1450
+ BASE_PRECISION, // baseAssetAmount: BN,
1451
+ PositionDirection.LONG,
1452
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
1453
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
1454
+ vBid,
1455
+ vAsk
1456
+ );
1457
+
1458
+ const nodesToTrigger = dlob.findNodesToTrigger(
1459
+ marketIndex,
1460
+ slot,
1461
+ oracle.price,
1462
+ MarketType.PERP
1463
+ );
1464
+ console.log(`nodesToTriggeR: ${nodesToTrigger.length}`);
1465
+ for (const [idx, n] of nodesToTrigger.entries()) {
1466
+ expect(n.node.order?.orderId.toNumber()).to.equal(orderIdsToTrigger[idx]);
1467
+ console.log(`nodeToTrigger: ${n.node.order?.orderId.toString()}`);
1468
+ }
1469
+ });
1470
+
1471
+ it('Test will return expired market orders to fill', () => {
1472
+ const vAsk = new BN(15);
1473
+ const vBid = new BN(8);
1474
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
1475
+ const marketIndex = new BN(0);
1476
+
1477
+ const slot = 20;
1478
+ const timeInForce = 30;
1479
+
1480
+ // non crossing bid
1481
+ insertOrderToDLOB(
1482
+ dlob,
1483
+ Keypair.generate().publicKey,
1484
+ OrderType.MARKET,
1485
+ MarketType.PERP,
1486
+ new BN(255), // orderId
1487
+ marketIndex,
1488
+ new BN(2), // price, very low, don't cross vamm
1489
+ BASE_PRECISION, // quantity
1490
+ PositionDirection.LONG,
1491
+ vBid,
1492
+ vAsk,
1493
+ new BN(slot),
1494
+ timeInForce
1495
+ );
1496
+ insertOrderToDLOB(
1497
+ dlob,
1498
+ Keypair.generate().publicKey,
1499
+ OrderType.MARKET,
1500
+ MarketType.PERP,
1501
+ new BN(2), // orderId
1502
+ marketIndex,
1503
+ new BN(30), // price, very high, don't cross vamm
1504
+ BASE_PRECISION, // quantity
1505
+ PositionDirection.SHORT,
1506
+ vAsk,
1507
+ vBid,
1508
+ new BN(slot),
1509
+ timeInForce
1510
+ );
1511
+
1512
+ // order auction is not yet complete, and order is not expired.
1513
+ const slot0 = slot;
1514
+ const nodesToFillBefore = dlob.findNodesToFill(
1515
+ marketIndex,
1516
+ vBid,
1517
+ vAsk,
1518
+ slot0,
1519
+ MarketType.PERP,
1520
+ {
1521
+ price: vBid.add(vAsk).div(new BN(2)),
1522
+ slot: new BN(slot0),
1523
+ confidence: new BN(1),
1524
+ hasSufficientNumberOfDataPoints: true,
1525
+ }
1526
+ );
1527
+ expect(nodesToFillBefore.length).to.equal(0);
1528
+
1529
+ // should get order to fill after timeInForce
1530
+ const slot1 = slot0 + timeInForce; // overshoots expiry
1531
+ const nodesToFillAfter = dlob.findNodesToFill(
1532
+ marketIndex,
1533
+ vBid,
1534
+ vAsk,
1535
+ slot1, // auction is over, and order ix expired
1536
+ MarketType.PERP,
1537
+ {
1538
+ price: vBid.add(vAsk).div(new BN(2)),
1539
+ slot: new BN(slot1),
1540
+ confidence: new BN(1),
1541
+ hasSufficientNumberOfDataPoints: true,
1542
+ }
1543
+ );
1544
+ expect(nodesToFillAfter.length).to.equal(2);
1545
+
1546
+ // check that the nodes have no makers
1547
+ expect(nodesToFillAfter[0].makerNode).to.equal(undefined);
1548
+ expect(nodesToFillAfter[1].makerNode).to.equal(undefined);
1549
+ });
1550
+ });
1551
+
1552
+ describe('DLOB Spot Tests', () => {
1553
+ it('Test proper bids', () => {
1554
+ const vAsk = new BN(115);
1555
+ const vBid = new BN(100);
1556
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
1557
+ const marketIndex = new BN(0);
1558
+
1559
+ const slot = 12;
1560
+ const oracle = {
1561
+ price: vBid.add(vAsk).div(new BN(2)),
1562
+ slot: new BN(slot),
1563
+ confidence: new BN(1),
1564
+ hasSufficientNumberOfDataPoints: true,
1565
+ };
1566
+ const testCases = [
1567
+ {
1568
+ expectedIdx: 3,
1569
+ orderId: new BN(5),
1570
+ price: new BN(0), // will calc 108
1571
+ direction: PositionDirection.LONG,
1572
+ orderType: OrderType.MARKET,
1573
+ },
1574
+ {
1575
+ expectedIdx: 4,
1576
+ orderId: new BN(6),
1577
+ price: new BN(0), // will calc 108
1578
+ direction: PositionDirection.LONG,
1579
+ orderType: OrderType.MARKET,
1580
+ },
1581
+ {
1582
+ expectedIdx: 5,
1583
+ orderId: new BN(7),
1584
+ price: new BN(0), // will calc 108
1585
+ direction: PositionDirection.LONG,
1586
+ orderType: OrderType.MARKET,
1587
+ },
1588
+ {
1589
+ expectedIdx: 0,
1590
+ orderId: new BN(1),
1591
+ price: new BN(110),
1592
+ direction: PositionDirection.LONG,
1593
+ orderType: OrderType.LIMIT,
1594
+ },
1595
+ {
1596
+ expectedIdx: 1,
1597
+ orderId: new BN(2),
1598
+ price: new BN(109),
1599
+ direction: PositionDirection.LONG,
1600
+ orderType: OrderType.LIMIT,
1601
+ },
1602
+ {
1603
+ expectedIdx: 6,
1604
+ orderId: new BN(3),
1605
+ price: new BN(107),
1606
+ direction: PositionDirection.LONG,
1607
+ orderType: OrderType.LIMIT,
1608
+ },
1609
+ {
1610
+ expectedIdx: 7,
1611
+ orderId: new BN(4),
1612
+ price: new BN(106),
1613
+ direction: PositionDirection.LONG,
1614
+ orderType: OrderType.LIMIT,
1615
+ },
1616
+ ];
1617
+
1618
+ for (const t of testCases) {
1619
+ insertOrderToDLOB(
1620
+ dlob,
1621
+ Keypair.generate().publicKey,
1622
+ t.orderType || OrderType.LIMIT,
1623
+ MarketType.SPOT,
1624
+ t.orderId || new BN(0), // orderId
1625
+ marketIndex,
1626
+ t.price || new BN(0), // price
1627
+ BASE_PRECISION, // quantity
1628
+ t.direction || PositionDirection.LONG,
1629
+ vBid,
1630
+ vAsk
1631
+ );
1632
+ }
1633
+
1634
+ const expectedTestCase = testCases.sort((a, b) => {
1635
+ // ascending order
1636
+ return a.expectedIdx - b.expectedIdx;
1637
+ });
1638
+ const bids = dlob.getBids(
1639
+ marketIndex,
1640
+ undefined,
1641
+ slot,
1642
+ MarketType.SPOT,
1643
+ oracle
1644
+ );
1645
+
1646
+ console.log('The Book Bids:');
1647
+ let countBids = 0;
1648
+ for (const bid of bids) {
1649
+ console.log(
1650
+ ` . vAMMNode? ${bid.isVammNode()}, ${JSON.stringify(
1651
+ bid.order?.orderType
1652
+ )} , ${bid.order?.orderId.toString()} , vammTestgetPRice: ${bid.getPrice(
1653
+ oracle,
1654
+ slot
1655
+ )}, price: ${bid.order?.price.toString()}, quantity: ${bid.order?.baseAssetAmountFilled.toString()}/${bid.order?.baseAssetAmount.toString()}`
1656
+ );
1657
+
1658
+ expect(bid.order?.orderId.toNumber()).to.equal(
1659
+ expectedTestCase[countBids].orderId?.toNumber()
1660
+ );
1661
+ expect(bid.order?.price.toNumber()).to.equal(
1662
+ expectedTestCase[countBids].price?.toNumber()
1663
+ );
1664
+ expect(bid.order?.direction).to.equal(
1665
+ expectedTestCase[countBids].direction
1666
+ );
1667
+ expect(bid.order?.orderType).to.equal(
1668
+ expectedTestCase[countBids].orderType
1669
+ );
1670
+ countBids++;
1671
+ }
1672
+
1673
+ expect(countBids).to.equal(testCases.length);
1674
+ });
1675
+
1676
+ it('Test proper bids on multiple markets', () => {
1677
+ const vAsk = new BN(15);
1678
+ const vBid = new BN(10);
1679
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
1680
+ const marketIndex0 = new BN(0);
1681
+ const marketIndex1 = new BN(1);
1682
+
1683
+ const slot = 12;
1684
+ const oracle = {
1685
+ price: vBid.add(vAsk).div(new BN(2)),
1686
+ slot: new BN(slot),
1687
+ confidence: new BN(1),
1688
+ hasSufficientNumberOfDataPoints: true,
1689
+ };
1690
+ const testCases = [
1691
+ {
1692
+ expectedIdx: 3,
1693
+ orderId: new BN(5),
1694
+ price: new BN(0),
1695
+ direction: PositionDirection.LONG,
1696
+ orderType: OrderType.MARKET,
1697
+ marketIndex: marketIndex0,
1698
+ },
1699
+ {
1700
+ expectedIdx: 4,
1701
+ orderId: new BN(6),
1702
+ price: new BN(0),
1703
+ direction: PositionDirection.LONG,
1704
+ orderType: OrderType.MARKET,
1705
+ marketIndex: marketIndex0,
1706
+ },
1707
+ {
1708
+ expectedIdx: 5,
1709
+ orderId: new BN(7),
1710
+ price: new BN(0),
1711
+ direction: PositionDirection.LONG,
1712
+ orderType: OrderType.MARKET,
1713
+ marketIndex: marketIndex1,
1714
+ },
1715
+ {
1716
+ expectedIdx: 0,
1717
+ orderId: new BN(1),
1718
+ price: new BN(12),
1719
+ direction: PositionDirection.LONG,
1720
+ orderType: OrderType.LIMIT,
1721
+ marketIndex: marketIndex0,
1722
+ },
1723
+ {
1724
+ expectedIdx: 1,
1725
+ orderId: new BN(2),
1726
+ price: new BN(11),
1727
+ direction: PositionDirection.LONG,
1728
+ orderType: OrderType.LIMIT,
1729
+ marketIndex: marketIndex0,
1730
+ },
1731
+ {
1732
+ expectedIdx: 7,
1733
+ orderId: new BN(3),
1734
+ price: new BN(8),
1735
+ direction: PositionDirection.LONG,
1736
+ orderType: OrderType.LIMIT,
1737
+ marketIndex: marketIndex0,
1738
+ },
1739
+ {
1740
+ expectedIdx: 6,
1741
+ orderId: new BN(4),
1742
+ price: new BN(9),
1743
+ direction: PositionDirection.LONG,
1744
+ orderType: OrderType.LIMIT,
1745
+ marketIndex: marketIndex1,
1746
+ },
1747
+ ];
1748
+
1749
+ for (const t of testCases) {
1750
+ insertOrderToDLOB(
1751
+ dlob,
1752
+ Keypair.generate().publicKey,
1753
+ t.orderType || OrderType.LIMIT,
1754
+ MarketType.SPOT,
1755
+ t.orderId || new BN(0), // orderId
1756
+ t.marketIndex,
1757
+ t.price || new BN(0), // price
1758
+ BASE_PRECISION, // quantity
1759
+ t.direction || PositionDirection.LONG,
1760
+ vBid,
1761
+ vAsk
1762
+ );
1763
+ }
1764
+
1765
+ const bids0 = dlob.getBids(
1766
+ marketIndex0,
1767
+ vBid,
1768
+ slot,
1769
+ MarketType.SPOT,
1770
+ oracle
1771
+ );
1772
+ let countBids0 = 0;
1773
+ for (const bid of bids0) {
1774
+ console.log(
1775
+ ` . vAMMNode? ${bid.isVammNode()}, ${JSON.stringify(
1776
+ bid.order?.orderType
1777
+ )} , ${bid.order?.orderId.toString()} , vammTestgetPRice: ${bid.getPrice(
1778
+ oracle,
1779
+ slot
1780
+ )}, price: ${bid.order?.price.toString()}, quantity: ${bid.order?.baseAssetAmountFilled.toString()}/${bid.order?.baseAssetAmount.toString()}`
1781
+ );
1782
+ countBids0++;
1783
+ }
1784
+ expect(countBids0).to.equal(5);
1785
+
1786
+ const bids1 = dlob.getBids(
1787
+ marketIndex1,
1788
+ vBid,
1789
+ slot,
1790
+ MarketType.SPOT,
1791
+ oracle
1792
+ );
1793
+ let countBids1 = 0;
1794
+ for (const bid of bids1) {
1795
+ console.log(
1796
+ ` . vAMMNode? ${bid.isVammNode()}, ${JSON.stringify(
1797
+ bid.order?.orderType
1798
+ )} , ${bid.order?.orderId.toString()} , vammTestgetPRice: ${bid.getPrice(
1799
+ oracle,
1800
+ slot
1801
+ )}, price: ${bid.order?.price.toString()}, quantity: ${bid.order?.baseAssetAmountFilled.toString()}/${bid.order?.baseAssetAmount.toString()}`
1802
+ );
1803
+
1804
+ countBids1++;
1805
+ }
1806
+ expect(countBids1).to.equal(2);
1807
+ });
1808
+
1809
+ it('Test proper asks', () => {
1810
+ const vAsk = new BN(15);
1811
+ const vBid = new BN(10);
1812
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
1813
+ const marketIndex = new BN(0);
1814
+
1815
+ const slot = 12;
1816
+ const oracle = {
1817
+ price: vBid.add(vAsk).div(new BN(2)),
1818
+ slot: new BN(slot),
1819
+ confidence: new BN(1),
1820
+ hasSufficientNumberOfDataPoints: true,
1821
+ };
1822
+ const testCases = [
1823
+ {
1824
+ expectedIdx: 0,
1825
+ orderId: new BN(3),
1826
+ price: new BN(0),
1827
+ direction: PositionDirection.SHORT,
1828
+ orderType: OrderType.MARKET,
1829
+ },
1830
+ {
1831
+ expectedIdx: 1,
1832
+ orderId: new BN(4),
1833
+ price: new BN(0),
1834
+ direction: PositionDirection.SHORT,
1835
+ orderType: OrderType.MARKET,
1836
+ },
1837
+ {
1838
+ expectedIdx: 2,
1839
+ orderId: new BN(5),
1840
+ price: new BN(0),
1841
+ direction: PositionDirection.SHORT,
1842
+ orderType: OrderType.MARKET,
1843
+ },
1844
+ {
1845
+ expectedIdx: 3,
1846
+ orderId: new BN(1),
1847
+ price: new BN(13),
1848
+ direction: PositionDirection.SHORT,
1849
+ orderType: OrderType.LIMIT,
1850
+ },
1851
+ {
1852
+ expectedIdx: 6,
1853
+ orderId: new BN(6),
1854
+ price: new BN(16),
1855
+ direction: PositionDirection.SHORT,
1856
+ orderType: OrderType.LIMIT,
1857
+ },
1858
+ {
1859
+ expectedIdx: 7,
1860
+ orderId: new BN(7),
1861
+ price: new BN(17),
1862
+ direction: PositionDirection.SHORT,
1863
+ orderType: OrderType.LIMIT,
1864
+ },
1865
+ {
1866
+ expectedIdx: 4,
1867
+ orderId: new BN(2),
1868
+ price: new BN(14),
1869
+ direction: PositionDirection.SHORT,
1870
+ orderType: OrderType.LIMIT,
1871
+ },
1872
+ ];
1873
+
1874
+ for (const t of testCases) {
1875
+ insertOrderToDLOB(
1876
+ dlob,
1877
+ Keypair.generate().publicKey,
1878
+ t.orderType || OrderType.LIMIT,
1879
+ MarketType.SPOT,
1880
+ t.orderId || new BN(0), // orderId
1881
+ marketIndex,
1882
+ t.price || new BN(0), // price
1883
+ BASE_PRECISION, // quantity
1884
+ t.direction || PositionDirection.SHORT,
1885
+ vBid,
1886
+ vAsk
1887
+ );
1888
+ }
1889
+
1890
+ const expectedTestCase = testCases.sort((a, b) => {
1891
+ // ascending order
1892
+ return a.expectedIdx - b.expectedIdx;
1893
+ });
1894
+ const asks = dlob.getAsks(marketIndex, vAsk, slot, MarketType.SPOT, oracle);
1895
+
1896
+ console.log('The Book Asks:');
1897
+ let countAsks = 0;
1898
+ for (const ask of asks) {
1899
+ console.log(
1900
+ ` . vAMMNode? ${ask.isVammNode()}, ${JSON.stringify(
1901
+ ask.order?.orderType
1902
+ )} , ${ask.order?.orderId.toString()} , vammTestgetPRice: ${ask.getPrice(
1903
+ oracle,
1904
+ slot
1905
+ )}, price: ${ask.order?.price.toString()}, quantity: ${ask.order?.baseAssetAmountFilled.toString()}/${ask.order?.baseAssetAmount.toString()}`
1906
+ );
1907
+
1908
+ expect(ask.order?.orderId.toNumber()).to.equal(
1909
+ expectedTestCase[countAsks].orderId?.toNumber()
1910
+ );
1911
+ expect(ask.order?.price.toNumber()).to.equal(
1912
+ expectedTestCase[countAsks].price?.toNumber()
1913
+ );
1914
+ expect(ask.order?.direction).to.equal(
1915
+ expectedTestCase[countAsks].direction
1916
+ );
1917
+ expect(ask.order?.orderType).to.equal(
1918
+ expectedTestCase[countAsks].orderType
1919
+ );
1920
+ countAsks++;
1921
+ }
1922
+
1923
+ expect(countAsks).to.equal(testCases.length);
1924
+ });
1925
+
1926
+ it('Test insert market orders', () => {
1927
+ const slot = 12;
1928
+ const vAsk = new BN(110);
1929
+ const vBid = new BN(100);
1930
+ const oracle = {
1931
+ price: vBid.add(vAsk).div(new BN(2)),
1932
+ slot: new BN(slot),
1933
+ confidence: new BN(1),
1934
+ hasSufficientNumberOfDataPoints: true,
1935
+ };
1936
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
1937
+ const marketIndex = new BN(0);
1938
+
1939
+ // 3 mkt buys
1940
+ for (let i = 0; i < 3; i++) {
1941
+ insertOrderToDLOB(
1942
+ dlob,
1943
+ Keypair.generate().publicKey,
1944
+ OrderType.MARKET,
1945
+ MarketType.SPOT,
1946
+ new BN(i + 1),
1947
+ marketIndex,
1948
+ new BN(0),
1949
+ BASE_PRECISION,
1950
+ PositionDirection.LONG,
1951
+ vBid,
1952
+ vAsk
1953
+ );
1954
+ }
1955
+
1956
+ // 3 mkt sells
1957
+ for (let i = 0; i < 3; i++) {
1958
+ insertOrderToDLOB(
1959
+ dlob,
1960
+ Keypair.generate().publicKey,
1961
+ OrderType.MARKET,
1962
+ MarketType.SPOT,
1963
+ new BN(i + 1),
1964
+ marketIndex,
1965
+ new BN(0),
1966
+ BASE_PRECISION,
1967
+ PositionDirection.SHORT,
1968
+ vBid,
1969
+ vAsk
1970
+ );
1971
+ }
1972
+
1973
+ let asks = 0;
1974
+ for (const ask of dlob.getAsks(
1975
+ marketIndex,
1976
+ vAsk,
1977
+ 2,
1978
+ MarketType.SPOT,
1979
+ oracle
1980
+ )) {
1981
+ // vamm node is last in asks
1982
+ asks++;
1983
+
1984
+ if (ask.order) {
1985
+ // market orders
1986
+ expect(getVariant(ask.order?.status)).to.equal('open');
1987
+ expect(getVariant(ask.order?.orderType)).to.equal('market');
1988
+ expect(getVariant(ask.order?.direction)).to.equal('short');
1989
+ expect(ask.order?.orderId.toNumber()).to.equal(asks);
1990
+ }
1991
+ }
1992
+ expect(asks).to.equal(3);
1993
+
1994
+ let bids = 0;
1995
+ for (const bid of dlob.getBids(
1996
+ marketIndex,
1997
+ vBid,
1998
+ 2,
1999
+ MarketType.SPOT,
2000
+ oracle
2001
+ )) {
2002
+ // market orders
2003
+ expect(getVariant(bid.order?.status)).to.equal('open');
2004
+ expect(getVariant(bid.order?.orderType)).to.equal('market');
2005
+ expect(getVariant(bid.order?.direction)).to.equal('long');
2006
+ expect(bid.order?.orderId.toNumber()).to.equal(bids + 1);
2007
+ bids++;
2008
+ }
2009
+ expect(bids).to.equal(3); // 3 orders
2010
+ });
2011
+
2012
+ it('Test insert limit orders', () => {
2013
+ const vAsk = new BN(110);
2014
+ const vBid = new BN(100);
2015
+ const slot = 12;
2016
+ const oracle = {
2017
+ price: vBid.add(vAsk).div(new BN(2)),
2018
+ slot: new BN(slot),
2019
+ confidence: new BN(1),
2020
+ hasSufficientNumberOfDataPoints: true,
2021
+ };
2022
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
2023
+ const marketIndex = new BN(0);
2024
+ insertOrderToDLOB(
2025
+ dlob,
2026
+ Keypair.generate().publicKey,
2027
+ OrderType.LIMIT,
2028
+ MarketType.SPOT,
2029
+ new BN(3),
2030
+ marketIndex,
2031
+ new BN(50),
2032
+ BASE_PRECISION,
2033
+ PositionDirection.LONG,
2034
+ vBid,
2035
+ vAsk
2036
+ );
2037
+
2038
+ insertOrderToDLOB(
2039
+ dlob,
2040
+ Keypair.generate().publicKey,
2041
+ OrderType.LIMIT,
2042
+ MarketType.SPOT,
2043
+ new BN(2),
2044
+ marketIndex,
2045
+ new BN(60),
2046
+ BASE_PRECISION,
2047
+ PositionDirection.LONG,
2048
+ vBid,
2049
+ vAsk
2050
+ );
2051
+
2052
+ insertOrderToDLOB(
2053
+ dlob,
2054
+ Keypair.generate().publicKey,
2055
+ OrderType.LIMIT,
2056
+ MarketType.SPOT,
2057
+ new BN(1),
2058
+ marketIndex,
2059
+ new BN(70),
2060
+ BASE_PRECISION,
2061
+ PositionDirection.LONG,
2062
+ vBid,
2063
+ vAsk
2064
+ );
2065
+
2066
+ insertOrderToDLOB(
2067
+ dlob,
2068
+ Keypair.generate().publicKey,
2069
+ OrderType.LIMIT,
2070
+ MarketType.SPOT,
2071
+ new BN(1),
2072
+ marketIndex,
2073
+ new BN(120),
2074
+ BASE_PRECISION,
2075
+ PositionDirection.SHORT,
2076
+ vBid,
2077
+ vAsk
2078
+ );
2079
+
2080
+ insertOrderToDLOB(
2081
+ dlob,
2082
+ Keypair.generate().publicKey,
2083
+ OrderType.LIMIT,
2084
+ MarketType.SPOT,
2085
+ new BN(2),
2086
+ marketIndex,
2087
+ new BN(130),
2088
+ BASE_PRECISION,
2089
+ PositionDirection.SHORT,
2090
+ vBid,
2091
+ vAsk
2092
+ );
2093
+
2094
+ insertOrderToDLOB(
2095
+ dlob,
2096
+ Keypair.generate().publicKey,
2097
+ OrderType.LIMIT,
2098
+ MarketType.SPOT,
2099
+ new BN(3),
2100
+ marketIndex,
2101
+ new BN(140),
2102
+ BASE_PRECISION,
2103
+ PositionDirection.SHORT,
2104
+ vBid,
2105
+ vAsk
2106
+ );
2107
+
2108
+ let asks = 0;
2109
+ for (const ask of dlob.getAsks(
2110
+ marketIndex,
2111
+ vAsk,
2112
+ slot,
2113
+ MarketType.SPOT,
2114
+ oracle
2115
+ )) {
2116
+ if (!ask.order) {
2117
+ console.error('wtf ask vamm?');
2118
+ continue;
2119
+ }
2120
+ // market orders
2121
+ console.log(`ask price: ${ask.order?.price.toString()}`);
2122
+ expect(getVariant(ask.order?.status)).to.equal('open');
2123
+ expect(getVariant(ask.order?.orderType)).to.equal('limit');
2124
+ expect(getVariant(ask.order?.direction)).to.equal('short');
2125
+ expect(ask.order?.orderId.toNumber()).to.equal(asks + 1);
2126
+ expect(ask.order?.price.gt(vAsk)).to.equal(true);
2127
+ asks++;
2128
+ }
2129
+ expect(asks).to.equal(3);
2130
+
2131
+ let bids = 0;
2132
+ for (const bid of dlob.getBids(
2133
+ marketIndex,
2134
+ vBid,
2135
+ slot,
2136
+ MarketType.SPOT,
2137
+ oracle
2138
+ )) {
2139
+ if (!bid.order) {
2140
+ console.error('wtf bid vamm?');
2141
+ continue;
2142
+ }
2143
+ // market orders
2144
+ console.log(`bid price: ${bid.order?.price.toString()}`);
2145
+ expect(getVariant(bid.order?.status)).to.equal('open');
2146
+ expect(getVariant(bid.order?.orderType)).to.equal('limit');
2147
+ expect(getVariant(bid.order?.direction)).to.equal('long');
2148
+ expect(bid.order?.orderId.toNumber()).to.equal(bids + 1);
2149
+ expect(bid.order?.price.lt(vBid)).to.equal(true);
2150
+ bids++;
2151
+ }
2152
+ expect(bids).to.equal(3);
2153
+ });
2154
+
2155
+ it('Test multiple market orders fill with multiple limit orders', () => {
2156
+ const vAsk = new BN(15);
2157
+ const vBid = new BN(10);
2158
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
2159
+ const marketIndex = new BN(0);
2160
+
2161
+ // insert some limit buys above vamm bid, below ask
2162
+ insertOrderToDLOB(
2163
+ dlob,
2164
+ Keypair.generate().publicKey,
2165
+ OrderType.LIMIT,
2166
+ MarketType.SPOT,
2167
+ new BN(1), // orderId
2168
+ marketIndex,
2169
+ new BN(11), // price
2170
+ BASE_PRECISION, // quantity
2171
+ PositionDirection.LONG,
2172
+ vBid,
2173
+ vAsk
2174
+ );
2175
+ insertOrderToDLOB(
2176
+ dlob,
2177
+ Keypair.generate().publicKey,
2178
+ OrderType.LIMIT,
2179
+ MarketType.SPOT,
2180
+ new BN(2), // orderId
2181
+ marketIndex,
2182
+ new BN(12), // price
2183
+ BASE_PRECISION, // quantity
2184
+ PositionDirection.LONG,
2185
+ vBid,
2186
+ vAsk
2187
+ );
2188
+ insertOrderToDLOB(
2189
+ dlob,
2190
+ Keypair.generate().publicKey,
2191
+ OrderType.LIMIT,
2192
+ MarketType.SPOT,
2193
+ new BN(3), // orderId
2194
+ marketIndex,
2195
+ new BN(13), // price
2196
+ BASE_PRECISION, // quantity
2197
+ PositionDirection.LONG,
2198
+ vBid,
2199
+ vAsk
2200
+ );
2201
+
2202
+ // should have no crossing orders
2203
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
2204
+ marketIndex,
2205
+ vBid,
2206
+ vAsk,
2207
+ 12, // auction over
2208
+ MarketType.SPOT,
2209
+ {
2210
+ price: vBid.add(vAsk).div(new BN(2)),
2211
+ slot: new BN(12),
2212
+ confidence: new BN(1),
2213
+ hasSufficientNumberOfDataPoints: true,
2214
+ }
2215
+ );
2216
+ expect(nodesToFillBefore.length).to.equal(0);
2217
+
2218
+ // place two market sell order eating 2 of the limit orders
2219
+ insertOrderToDLOB(
2220
+ dlob,
2221
+ Keypair.generate().publicKey,
2222
+ OrderType.MARKET,
2223
+ MarketType.SPOT,
2224
+ new BN(4), // orderId
2225
+ marketIndex,
2226
+ new BN(12), // price
2227
+ new BN(1).mul(BASE_PRECISION), // quantity
2228
+ PositionDirection.SHORT,
2229
+ vBid,
2230
+ vAsk
2231
+ );
2232
+ insertOrderToDLOB(
2233
+ dlob,
2234
+ Keypair.generate().publicKey,
2235
+ OrderType.MARKET,
2236
+ MarketType.SPOT,
2237
+ new BN(5), // orderId
2238
+ marketIndex,
2239
+ new BN(12), // price
2240
+ new BN(1).mul(BASE_PRECISION), // quantity
2241
+ PositionDirection.SHORT,
2242
+ vBid,
2243
+ vAsk
2244
+ );
2245
+
2246
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
2247
+ marketIndex,
2248
+ vBid,
2249
+ vAsk,
2250
+ 12, // auction over
2251
+ MarketType.SPOT,
2252
+ {
2253
+ price: vBid.add(vAsk).div(new BN(2)),
2254
+ slot: new BN(12),
2255
+ confidence: new BN(1),
2256
+ hasSufficientNumberOfDataPoints: true,
2257
+ }
2258
+ );
2259
+ for (const n of nodesToFillAfter) {
2260
+ console.log(
2261
+ `cross found: taker orderId: ${n.node.order?.orderId.toString()}: BAA: ${n.node.order?.baseAssetAmount.toString()}, maker orderId: ${n.makerNode?.order?.orderId.toString()}: BAA: ${n.makerNode?.order?.baseAssetAmount.toString()}`
2262
+ );
2263
+ }
2264
+ expect(nodesToFillAfter.length).to.equal(2);
2265
+
2266
+ // first taker should fill with best maker
2267
+ expect(nodesToFillAfter[0].node.order?.orderId.toNumber()).to.equal(4);
2268
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId.toNumber()).to.equal(
2269
+ 3
2270
+ );
2271
+
2272
+ // second taker should fill with second best maker
2273
+ expect(nodesToFillAfter[1].node.order?.orderId.toNumber()).to.equal(5);
2274
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId.toNumber()).to.equal(
2275
+ 2
2276
+ );
2277
+ });
2278
+
2279
+ it('Test one market order fills two limit orders', () => {
2280
+ const vAsk = new BN(15);
2281
+ const vBid = new BN(10);
2282
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
2283
+ const marketIndex = new BN(0);
2284
+
2285
+ // insert some limit sells below vAMM ask, above bid
2286
+ insertOrderToDLOB(
2287
+ dlob,
2288
+ Keypair.generate().publicKey,
2289
+ OrderType.LIMIT,
2290
+ MarketType.SPOT,
2291
+ new BN(1), // orderId
2292
+ marketIndex,
2293
+ new BN(14), // price
2294
+ BASE_PRECISION, // quantity
2295
+ PositionDirection.SHORT,
2296
+ vBid,
2297
+ vAsk
2298
+ );
2299
+ insertOrderToDLOB(
2300
+ dlob,
2301
+ Keypair.generate().publicKey,
2302
+ OrderType.LIMIT,
2303
+ MarketType.SPOT,
2304
+ new BN(2), // orderId
2305
+ marketIndex,
2306
+ new BN(12), // price
2307
+ BASE_PRECISION, // quantity
2308
+ PositionDirection.SHORT,
2309
+ vBid,
2310
+ vAsk
2311
+ );
2312
+ insertOrderToDLOB(
2313
+ dlob,
2314
+ Keypair.generate().publicKey,
2315
+ OrderType.LIMIT,
2316
+ MarketType.SPOT,
2317
+ new BN(3), // orderId
2318
+ marketIndex,
2319
+ new BN(11), // price
2320
+ BASE_PRECISION, // quantity
2321
+ PositionDirection.SHORT,
2322
+ vBid,
2323
+ vAsk
2324
+ );
2325
+
2326
+ // should have no crossing orders
2327
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
2328
+ marketIndex,
2329
+ vBid,
2330
+ vAsk,
2331
+ 12, // auction over
2332
+ MarketType.SPOT,
2333
+ {
2334
+ price: vBid.add(vAsk).div(new BN(2)),
2335
+ slot: new BN(12),
2336
+ confidence: new BN(1),
2337
+ hasSufficientNumberOfDataPoints: true,
2338
+ }
2339
+ );
2340
+ expect(nodesToFillBefore.length).to.equal(0);
2341
+
2342
+ // place one market buy order eating 2 of the limit orders
2343
+ insertOrderToDLOB(
2344
+ dlob,
2345
+ Keypair.generate().publicKey,
2346
+ OrderType.MARKET,
2347
+ MarketType.SPOT,
2348
+ new BN(4), // orderId
2349
+ marketIndex,
2350
+ new BN(12), // price
2351
+ new BN(2).mul(BASE_PRECISION), // quantity
2352
+ PositionDirection.LONG,
2353
+ vBid,
2354
+ vAsk
2355
+ );
2356
+
2357
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
2358
+ marketIndex,
2359
+ vBid,
2360
+ vAsk,
2361
+ 12, // auction over
2362
+ MarketType.SPOT,
2363
+ {
2364
+ price: vBid.add(vAsk).div(new BN(2)),
2365
+ slot: new BN(12),
2366
+ confidence: new BN(1),
2367
+ hasSufficientNumberOfDataPoints: true,
2368
+ }
2369
+ );
2370
+ for (const n of nodesToFillAfter) {
2371
+ console.log(
2372
+ `cross found: taker orderId: ${n.node.order?.orderId.toString()}: BAA: ${n.node.order?.baseAssetAmountFilled.toString()}/${n.node.order?.baseAssetAmount.toString()}, maker orderId: ${n.makerNode?.order?.orderId.toString()}: BAA: ${n.makerNode?.order?.baseAssetAmountFilled.toString()}/${n.makerNode?.order?.baseAssetAmount.toString()}`
2373
+ );
2374
+ }
2375
+ expect(nodesToFillAfter.length).to.equal(2);
2376
+
2377
+ // taker should fill completely with best maker
2378
+ expect(nodesToFillAfter[0].node.order?.orderId.toNumber()).to.equal(4);
2379
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId.toNumber()).to.equal(
2380
+ 3
2381
+ );
2382
+
2383
+ // taker should fill completely with second best maker
2384
+ expect(nodesToFillAfter[1].node.order?.orderId.toNumber()).to.equal(4);
2385
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId.toNumber()).to.equal(
2386
+ 2
2387
+ );
2388
+ });
2389
+
2390
+ it('Test two market orders to fill one limit order', () => {
2391
+ const vAsk = new BN(15);
2392
+ const vBid = new BN(8);
2393
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
2394
+ const marketIndex = new BN(0);
2395
+
2396
+ const slot = 12;
2397
+ const oracle = {
2398
+ price: vBid.add(vAsk).div(new BN(2)),
2399
+ slot: new BN(slot),
2400
+ confidence: new BN(1),
2401
+ hasSufficientNumberOfDataPoints: true,
2402
+ };
2403
+
2404
+ // insert some limit sells below vAMM ask, above bid
2405
+ insertOrderToDLOB(
2406
+ dlob,
2407
+ Keypair.generate().publicKey,
2408
+ OrderType.LIMIT,
2409
+ MarketType.SPOT,
2410
+ new BN(1), // orderId
2411
+ marketIndex,
2412
+ new BN(14), // price
2413
+ BASE_PRECISION, // quantity
2414
+ PositionDirection.SHORT,
2415
+ vBid,
2416
+ vAsk
2417
+ );
2418
+ insertOrderToDLOB(
2419
+ dlob,
2420
+ Keypair.generate().publicKey,
2421
+ OrderType.LIMIT,
2422
+ MarketType.SPOT,
2423
+ new BN(2), // orderId
2424
+ marketIndex,
2425
+ new BN(13), // price
2426
+ BASE_PRECISION, // quantity
2427
+ PositionDirection.SHORT,
2428
+ vBid,
2429
+ vAsk
2430
+ );
2431
+ insertOrderToDLOB(
2432
+ dlob,
2433
+ Keypair.generate().publicKey,
2434
+ OrderType.LIMIT,
2435
+ MarketType.SPOT,
2436
+ new BN(3), // orderId
2437
+ marketIndex,
2438
+ new BN(8), // price <-- best price
2439
+ new BN(3).mul(BASE_PRECISION), // quantity
2440
+ PositionDirection.SHORT,
2441
+ vBid,
2442
+ vAsk
2443
+ );
2444
+
2445
+ // should have no crossing orders
2446
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
2447
+ marketIndex,
2448
+ vBid,
2449
+ vAsk,
2450
+ 12, // auction over
2451
+ MarketType.SPOT,
2452
+ oracle
2453
+ );
2454
+ expect(nodesToFillBefore.length).to.equal(0);
2455
+
2456
+ // place two market buy orders to eat the best ask
2457
+ insertOrderToDLOB(
2458
+ dlob,
2459
+ Keypair.generate().publicKey,
2460
+ OrderType.MARKET,
2461
+ MarketType.SPOT,
2462
+ new BN(4), // orderId
2463
+ marketIndex,
2464
+ new BN(0), // price
2465
+ new BN(1).mul(BASE_PRECISION), // quantity
2466
+ PositionDirection.LONG,
2467
+ vBid,
2468
+ vAsk
2469
+ );
2470
+ insertOrderToDLOB(
2471
+ dlob,
2472
+ Keypair.generate().publicKey,
2473
+ OrderType.MARKET,
2474
+ MarketType.SPOT,
2475
+ new BN(5), // orderId
2476
+ marketIndex,
2477
+ new BN(0), // price
2478
+ new BN(2).mul(BASE_PRECISION), // quantity
2479
+ PositionDirection.LONG,
2480
+ vBid,
2481
+ vAsk
2482
+ );
2483
+
2484
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
2485
+ marketIndex,
2486
+ vBid,
2487
+ vAsk,
2488
+ slot, // auction over
2489
+ MarketType.SPOT,
2490
+ oracle
2491
+ );
2492
+ const mktNodes = dlob.findMarketNodesToFill(
2493
+ marketIndex,
2494
+ slot,
2495
+ MarketType.SPOT
2496
+ );
2497
+ console.log(`market nodes: ${mktNodes.length}`);
2498
+
2499
+ const askNodes = dlob.getAsks(
2500
+ marketIndex,
2501
+ vAsk,
2502
+ slot,
2503
+ MarketType.SPOT,
2504
+ oracle
2505
+ );
2506
+ let aa = 0;
2507
+ for (const b of askNodes) {
2508
+ console.log(
2509
+ ` . ask: ${b.order?.orderId.toString()}: p1: ${b.order?.price.toString()} p2: ${b.getPrice(
2510
+ oracle,
2511
+ slot
2512
+ )}, ${JSON.stringify(
2513
+ b.order?.orderType
2514
+ )} BAA: ${b.order?.baseAssetAmountFilled.toString()}/${b.order?.baseAssetAmount.toString()}`
2515
+ );
2516
+ aa++;
2517
+ }
2518
+ expect(aa).to.equal(3);
2519
+
2520
+ const bidNodes = dlob.getBids(
2521
+ marketIndex,
2522
+ vBid,
2523
+ slot,
2524
+ MarketType.SPOT,
2525
+ oracle
2526
+ );
2527
+ let bb = 0;
2528
+ for (const b of bidNodes) {
2529
+ console.log(
2530
+ ` . bid: ${b.order?.orderId.toString()}: p1: ${b.order?.price.toString()} p2: ${b.getPrice(
2531
+ oracle,
2532
+ slot
2533
+ )}, ${JSON.stringify(
2534
+ b.order?.orderType
2535
+ )} BAA: ${b.order?.baseAssetAmountFilled.toString()}/${b.order?.baseAssetAmount.toString()}`
2536
+ );
2537
+ bb++;
2538
+ }
2539
+ expect(bb).to.equal(2);
2540
+
2541
+ console.log(`bids nodes: ${bb}`);
2542
+ for (const n of nodesToFillAfter) {
2543
+ console.log(
2544
+ `cross found: taker orderId: ${n.node.order?.orderId.toString()}: BAA: ${n.node.order?.baseAssetAmountFilled.toString()}/${n.node.order?.baseAssetAmount.toString()}, maker orderId: ${n.makerNode?.order?.orderId.toString()}: BAA: ${n.makerNode?.order?.baseAssetAmountFilled.toString()}/${n.makerNode?.order?.baseAssetAmount.toString()}`
2545
+ );
2546
+ }
2547
+ expect(nodesToFillAfter.length).to.equal(2);
2548
+
2549
+ // taker should fill completely with best maker
2550
+ expect(nodesToFillAfter[0].node.order?.orderId.toNumber()).to.equal(4);
2551
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId.toNumber()).to.equal(
2552
+ 3
2553
+ );
2554
+
2555
+ // taker should fill completely with second best maker
2556
+ expect(nodesToFillAfter[1].node.order?.orderId.toNumber()).to.equal(5);
2557
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId.toNumber()).to.equal(
2558
+ 3
2559
+ );
2560
+ });
2561
+
2562
+ it('Test trigger orders', () => {
2563
+ const vAsk = new BN(15);
2564
+ const vBid = new BN(8);
2565
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
2566
+ const marketIndex = new BN(0);
2567
+
2568
+ const slot = 20;
2569
+ const oracle = {
2570
+ price: vBid.add(vAsk).div(new BN(2)),
2571
+ slot: new BN(slot),
2572
+ confidence: new BN(1),
2573
+ hasSufficientNumberOfDataPoints: true,
2574
+ };
2575
+
2576
+ const orderIdsToTrigger = [1, 2, 3, 4, 5, 6, 7, 8];
2577
+ // const orderIdsToNotTrigger = [9, 10, 11, 12];
2578
+
2579
+ // should trigger limit buy with above condition
2580
+ insertTriggerOrderToDLOB(
2581
+ dlob,
2582
+ Keypair.generate().publicKey,
2583
+ OrderType.TRIGGER_LIMIT,
2584
+ MarketType.SPOT,
2585
+ new BN(1), //orderId
2586
+ marketIndex, // marketIndex
2587
+ vBid, // price
2588
+ BASE_PRECISION, // baseAssetAmount: BN,
2589
+ PositionDirection.LONG,
2590
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
2591
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
2592
+ vBid,
2593
+ vAsk
2594
+ );
2595
+ // should trigger limit sell with above condition
2596
+ insertTriggerOrderToDLOB(
2597
+ dlob,
2598
+ Keypair.generate().publicKey,
2599
+ OrderType.TRIGGER_LIMIT,
2600
+ MarketType.SPOT,
2601
+ new BN(2), //orderId
2602
+ marketIndex, // marketIndex
2603
+ vBid, // price
2604
+ BASE_PRECISION, // baseAssetAmount: BN,
2605
+ PositionDirection.SHORT,
2606
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
2607
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
2608
+ vBid,
2609
+ vAsk
2610
+ );
2611
+ // should trigger market buy with above condition
2612
+ insertTriggerOrderToDLOB(
2613
+ dlob,
2614
+ Keypair.generate().publicKey,
2615
+ OrderType.TRIGGER_MARKET,
2616
+ MarketType.SPOT,
2617
+ new BN(3), //orderId
2618
+ marketIndex, // marketIndex
2619
+ vAsk, // price
2620
+ BASE_PRECISION, // baseAssetAmount: BN,
2621
+ PositionDirection.LONG,
2622
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
2623
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
2624
+ vBid,
2625
+ vAsk
2626
+ );
2627
+ // should trigger market sell with above condition
2628
+ insertTriggerOrderToDLOB(
2629
+ dlob,
2630
+ Keypair.generate().publicKey,
2631
+ OrderType.TRIGGER_MARKET,
2632
+ MarketType.SPOT,
2633
+ new BN(4), //orderId
2634
+ marketIndex, // marketIndex
2635
+ vBid, // price
2636
+ BASE_PRECISION, // baseAssetAmount: BN,
2637
+ PositionDirection.SHORT,
2638
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
2639
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
2640
+ vBid,
2641
+ vAsk
2642
+ );
2643
+ // should trigger limit buy with below condition
2644
+ insertTriggerOrderToDLOB(
2645
+ dlob,
2646
+ Keypair.generate().publicKey,
2647
+ OrderType.TRIGGER_LIMIT,
2648
+ MarketType.SPOT,
2649
+ new BN(5), //orderId
2650
+ marketIndex, // marketIndex
2651
+ vBid, // price
2652
+ BASE_PRECISION, // baseAssetAmount: BN,
2653
+ PositionDirection.LONG,
2654
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
2655
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
2656
+ vBid,
2657
+ vAsk
2658
+ );
2659
+ // should trigger limit sell with below condition
2660
+ insertTriggerOrderToDLOB(
2661
+ dlob,
2662
+ Keypair.generate().publicKey,
2663
+ OrderType.TRIGGER_LIMIT,
2664
+ MarketType.SPOT,
2665
+ new BN(6), //orderId
2666
+ marketIndex, // marketIndex
2667
+ vBid, // price
2668
+ BASE_PRECISION, // baseAssetAmount: BN,
2669
+ PositionDirection.SHORT,
2670
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
2671
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
2672
+ vBid,
2673
+ vAsk
2674
+ );
2675
+ // should trigger market buy with below condition
2676
+ insertTriggerOrderToDLOB(
2677
+ dlob,
2678
+ Keypair.generate().publicKey,
2679
+ OrderType.TRIGGER_MARKET,
2680
+ MarketType.SPOT,
2681
+ new BN(7), //orderId
2682
+ marketIndex, // marketIndex
2683
+ vBid, // price
2684
+ BASE_PRECISION, // baseAssetAmount: BN,
2685
+ PositionDirection.LONG,
2686
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
2687
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
2688
+ vBid,
2689
+ vAsk
2690
+ );
2691
+ // should trigger market sell with below condition
2692
+ insertTriggerOrderToDLOB(
2693
+ dlob,
2694
+ Keypair.generate().publicKey,
2695
+ OrderType.TRIGGER_MARKET,
2696
+ MarketType.SPOT,
2697
+ new BN(8), //orderId
2698
+ marketIndex, // marketIndex
2699
+ vBid, // price
2700
+ BASE_PRECISION, // baseAssetAmount: BN,
2701
+ PositionDirection.LONG,
2702
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
2703
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
2704
+ vBid,
2705
+ vAsk
2706
+ );
2707
+
2708
+ // should NOT trigger market sell with above condition
2709
+ insertTriggerOrderToDLOB(
2710
+ dlob,
2711
+ Keypair.generate().publicKey,
2712
+ OrderType.TRIGGER_MARKET,
2713
+ MarketType.SPOT,
2714
+ new BN(9), //orderId
2715
+ marketIndex, // marketIndex
2716
+ vBid, // price
2717
+ BASE_PRECISION, // baseAssetAmount: BN,
2718
+ PositionDirection.SHORT,
2719
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
2720
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
2721
+ vBid,
2722
+ vAsk
2723
+ );
2724
+ // should NOT trigger market sell with below condition
2725
+ insertTriggerOrderToDLOB(
2726
+ dlob,
2727
+ Keypair.generate().publicKey,
2728
+ OrderType.TRIGGER_MARKET,
2729
+ MarketType.SPOT,
2730
+ new BN(10), //orderId
2731
+ marketIndex, // marketIndex
2732
+ vBid, // price
2733
+ BASE_PRECISION, // baseAssetAmount: BN,
2734
+ PositionDirection.SHORT,
2735
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
2736
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
2737
+ vBid,
2738
+ vAsk
2739
+ );
2740
+ // should NOT trigger market buy with above condition
2741
+ insertTriggerOrderToDLOB(
2742
+ dlob,
2743
+ Keypair.generate().publicKey,
2744
+ OrderType.TRIGGER_MARKET,
2745
+ MarketType.SPOT,
2746
+ new BN(11), //orderId
2747
+ marketIndex, // marketIndex
2748
+ vBid, // price
2749
+ BASE_PRECISION, // baseAssetAmount: BN,
2750
+ PositionDirection.LONG,
2751
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
2752
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
2753
+ vBid,
2754
+ vAsk
2755
+ );
2756
+ // should NOT trigger market buy with below condition
2757
+ insertTriggerOrderToDLOB(
2758
+ dlob,
2759
+ Keypair.generate().publicKey,
2760
+ OrderType.TRIGGER_MARKET,
2761
+ MarketType.SPOT,
2762
+ new BN(12), //orderId
2763
+ marketIndex, // marketIndex
2764
+ vBid, // price
2765
+ BASE_PRECISION, // baseAssetAmount: BN,
2766
+ PositionDirection.LONG,
2767
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
2768
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
2769
+ vBid,
2770
+ vAsk
2771
+ );
2772
+
2773
+ const nodesToTrigger = dlob.findNodesToTrigger(
2774
+ marketIndex,
2775
+ slot,
2776
+ oracle.price,
2777
+ MarketType.SPOT
2778
+ );
2779
+ console.log(`nodesToTriggeR: ${nodesToTrigger.length}`);
2780
+ for (const [idx, n] of nodesToTrigger.entries()) {
2781
+ expect(n.node.order?.orderId.toNumber()).to.equal(orderIdsToTrigger[idx]);
2782
+ console.log(`nodeToTrigger: ${n.node.order?.orderId.toString()}`);
2783
+ }
2784
+ });
2785
+
2786
+ it('Test will return expired market orders to fill', () => {
2787
+ const vAsk = new BN(15);
2788
+ const vBid = new BN(8);
2789
+ const dlob = new DLOB(mockPerpMarkets, mockSpotMarkets, false);
2790
+ const marketIndex = new BN(0);
2791
+
2792
+ const slot = 20;
2793
+ const timeInForce = 30;
2794
+
2795
+ // non crossing bid
2796
+ insertOrderToDLOB(
2797
+ dlob,
2798
+ Keypair.generate().publicKey,
2799
+ OrderType.MARKET,
2800
+ MarketType.SPOT,
2801
+ new BN(255), // orderId
2802
+ marketIndex,
2803
+ new BN(2), // price, very low, don't cross vamm
2804
+ BASE_PRECISION, // quantity
2805
+ PositionDirection.LONG,
2806
+ vBid,
2807
+ vAsk,
2808
+ new BN(slot),
2809
+ timeInForce
2810
+ );
2811
+ insertOrderToDLOB(
2812
+ dlob,
2813
+ Keypair.generate().publicKey,
2814
+ OrderType.MARKET,
2815
+ MarketType.SPOT,
2816
+ new BN(2), // orderId
2817
+ marketIndex,
2818
+ new BN(30), // price, very high, don't cross vamm
2819
+ BASE_PRECISION, // quantity
2820
+ PositionDirection.SHORT,
2821
+ vAsk,
2822
+ vBid,
2823
+ new BN(slot),
2824
+ timeInForce
2825
+ );
2826
+
2827
+ // order auction is not yet complete, and order is not expired.
2828
+ const slot0 = slot;
2829
+ const nodesToFillBefore = dlob.findNodesToFill(
2830
+ marketIndex,
2831
+ vBid,
2832
+ vAsk,
2833
+ slot0,
2834
+ MarketType.SPOT,
2835
+ {
2836
+ price: vBid.add(vAsk).div(new BN(2)),
2837
+ slot: new BN(slot0),
2838
+ confidence: new BN(1),
2839
+ hasSufficientNumberOfDataPoints: true,
2840
+ }
2841
+ );
2842
+ expect(nodesToFillBefore.length).to.equal(0);
2843
+
2844
+ // should get order to fill after timeInForce
2845
+ const slot1 = slot0 + timeInForce; // overshoots expiry
2846
+ const nodesToFillAfter = dlob.findNodesToFill(
2847
+ marketIndex,
2848
+ vBid,
2849
+ vAsk,
2850
+ slot1,
2851
+ MarketType.SPOT,
2852
+ {
2853
+ price: vBid.add(vAsk).div(new BN(2)),
2854
+ slot: new BN(slot1),
2855
+ confidence: new BN(1),
2856
+ hasSufficientNumberOfDataPoints: true,
2857
+ }
2858
+ );
2859
+ expect(nodesToFillAfter.length).to.equal(2);
2860
+
2861
+ // check that the nodes have no makers
2862
+ expect(nodesToFillAfter[0].makerNode).to.equal(undefined);
2863
+ expect(nodesToFillAfter[1].makerNode).to.equal(undefined);
2864
+ });
2865
+ });