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

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