@drift-labs/sdk 0.2.0-master.4 → 0.2.0-master.40

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 (234) hide show
  1. package/README.md +27 -27
  2. package/lib/accounts/bulkAccountLoader.d.ts +2 -0
  3. package/lib/accounts/bulkAccountLoader.js +36 -29
  4. package/lib/accounts/bulkUserStatsSubscription.d.ts +7 -0
  5. package/lib/accounts/bulkUserStatsSubscription.js +21 -0
  6. package/lib/accounts/bulkUserSubscription.d.ts +2 -2
  7. package/lib/accounts/bulkUserSubscription.js +0 -1
  8. package/lib/accounts/fetch.d.ts +2 -1
  9. package/lib/accounts/fetch.js +9 -1
  10. package/lib/accounts/{pollingClearingHouseAccountSubscriber.d.ts → pollingDriftClientAccountSubscriber.d.ts} +20 -25
  11. package/lib/accounts/{pollingClearingHouseAccountSubscriber.js → pollingDriftClientAccountSubscriber.js} +45 -49
  12. package/lib/accounts/{pollingOracleSubscriber.d.ts → pollingOracleAccountSubscriber.d.ts} +2 -2
  13. package/lib/accounts/{pollingOracleSubscriber.js → pollingOracleAccountSubscriber.js} +3 -3
  14. package/lib/accounts/pollingUserStatsAccountSubscriber.d.ts +27 -0
  15. package/lib/accounts/pollingUserStatsAccountSubscriber.js +113 -0
  16. package/lib/accounts/types.d.ts +26 -15
  17. package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +49 -0
  18. package/lib/accounts/{webSocketClearingHouseAccountSubscriber.js → webSocketDriftClientAccountSubscriber.js} +47 -45
  19. package/lib/accounts/webSocketUserStatsAccountSubsriber.d.ts +20 -0
  20. package/lib/accounts/webSocketUserStatsAccountSubsriber.js +47 -0
  21. package/lib/addresses/marketAddresses.d.ts +1 -3
  22. package/lib/addresses/marketAddresses.js +1 -1
  23. package/lib/addresses/pda.d.ts +15 -9
  24. package/lib/addresses/pda.js +73 -35
  25. package/lib/adminClient.d.ts +65 -0
  26. package/lib/adminClient.js +637 -0
  27. package/lib/config.d.ts +9 -9
  28. package/lib/config.js +25 -21
  29. package/lib/constants/numericConstants.d.ts +30 -12
  30. package/lib/constants/numericConstants.js +41 -21
  31. package/lib/constants/perpMarkets.d.ts +18 -0
  32. package/lib/constants/{markets.js → perpMarkets.js} +7 -7
  33. package/lib/constants/spotMarkets.d.ts +19 -0
  34. package/lib/constants/spotMarkets.js +53 -0
  35. package/lib/dlob/DLOB.d.ts +82 -0
  36. package/lib/dlob/DLOB.js +696 -0
  37. package/lib/dlob/DLOBNode.d.ts +54 -0
  38. package/lib/dlob/DLOBNode.js +77 -0
  39. package/lib/dlob/NodeList.d.ts +27 -0
  40. package/lib/dlob/NodeList.js +144 -0
  41. package/lib/driftClient.d.ts +233 -0
  42. package/lib/driftClient.js +2096 -0
  43. package/lib/{clearingHouseConfig.d.ts → driftClientConfig.d.ts} +9 -9
  44. package/lib/{clearingHouseConfig.js → driftClientConfig.js} +0 -0
  45. package/lib/events/eventList.js +3 -0
  46. package/lib/events/eventSubscriber.d.ts +5 -2
  47. package/lib/events/eventSubscriber.js +25 -11
  48. package/lib/events/fetchLogs.d.ts +13 -2
  49. package/lib/events/fetchLogs.js +40 -12
  50. package/lib/events/pollingLogProvider.d.ts +2 -1
  51. package/lib/events/pollingLogProvider.js +7 -3
  52. package/lib/events/sort.js +8 -11
  53. package/lib/events/types.d.ts +9 -3
  54. package/lib/events/types.js +6 -0
  55. package/lib/events/webSocketLogProvider.js +1 -1
  56. package/lib/examples/makeTradeExample.js +30 -18
  57. package/lib/factory/bigNum.d.ts +8 -4
  58. package/lib/factory/bigNum.js +109 -19
  59. package/lib/idl/drift.json +8250 -0
  60. package/lib/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  61. package/lib/index.d.ts +30 -13
  62. package/lib/index.js +30 -13
  63. package/lib/math/amm.d.ts +9 -6
  64. package/lib/math/amm.js +91 -38
  65. package/lib/math/conversion.js +1 -1
  66. package/lib/math/exchangeStatus.d.ts +4 -0
  67. package/lib/math/exchangeStatus.js +18 -0
  68. package/lib/math/funding.d.ts +6 -6
  69. package/lib/math/funding.js +23 -21
  70. package/lib/math/insurance.d.ts +4 -0
  71. package/lib/math/insurance.js +27 -0
  72. package/lib/math/margin.d.ts +11 -0
  73. package/lib/math/margin.js +82 -0
  74. package/lib/math/market.d.ts +14 -9
  75. package/lib/math/market.js +70 -10
  76. package/lib/math/oracles.d.ts +4 -0
  77. package/lib/math/oracles.js +36 -8
  78. package/lib/math/orders.d.ts +14 -6
  79. package/lib/math/orders.js +90 -17
  80. package/lib/math/position.d.ts +27 -13
  81. package/lib/math/position.js +92 -36
  82. package/lib/math/repeg.js +17 -8
  83. package/lib/math/spotBalance.d.ts +22 -0
  84. package/lib/math/spotBalance.js +192 -0
  85. package/lib/math/spotMarket.d.ts +4 -0
  86. package/lib/math/spotMarket.js +8 -0
  87. package/lib/math/spotPosition.d.ts +6 -0
  88. package/lib/math/spotPosition.js +23 -0
  89. package/lib/math/trade.d.ts +10 -10
  90. package/lib/math/trade.js +27 -31
  91. package/lib/oracles/pythClient.js +1 -1
  92. package/lib/oracles/quoteAssetOracleClient.js +1 -1
  93. package/lib/oracles/switchboardClient.js +1 -1
  94. package/lib/orderParams.d.ts +4 -4
  95. package/lib/orderParams.js +12 -4
  96. package/lib/serum/serumFulfillmentConfigMap.d.ts +10 -0
  97. package/lib/serum/serumFulfillmentConfigMap.js +17 -0
  98. package/lib/serum/serumSubscriber.d.ts +27 -0
  99. package/lib/serum/serumSubscriber.js +56 -0
  100. package/lib/serum/types.d.ts +11 -0
  101. package/lib/{clearingHouseUserConfig.js → serum/types.js} +0 -0
  102. package/lib/slot/SlotSubscriber.d.ts +7 -0
  103. package/lib/slot/SlotSubscriber.js +3 -0
  104. package/lib/{mockUSDCFaucet.d.ts → tokenFaucet.d.ts} +8 -5
  105. package/lib/{mockUSDCFaucet.js → tokenFaucet.js} +63 -51
  106. package/lib/tx/retryTxSender.d.ts +1 -1
  107. package/lib/tx/retryTxSender.js +13 -4
  108. package/lib/tx/types.d.ts +1 -1
  109. package/lib/tx/utils.js +1 -1
  110. package/lib/types.d.ts +589 -196
  111. package/lib/types.js +108 -17
  112. package/lib/user.d.ts +226 -0
  113. package/lib/user.js +949 -0
  114. package/lib/userConfig.d.ts +14 -0
  115. package/lib/userConfig.js +2 -0
  116. package/lib/userMap/userMap.d.ts +41 -0
  117. package/lib/userMap/userMap.js +85 -0
  118. package/lib/userMap/userStatsMap.d.ts +19 -0
  119. package/lib/userMap/userStatsMap.js +68 -0
  120. package/lib/userName.d.ts +1 -0
  121. package/lib/userName.js +3 -2
  122. package/lib/userStats.d.ts +18 -0
  123. package/lib/userStats.js +49 -0
  124. package/lib/userStatsConfig.d.ts +14 -0
  125. package/lib/userStatsConfig.js +2 -0
  126. package/lib/util/computeUnits.js +1 -1
  127. package/lib/util/getTokenAddress.d.ts +2 -0
  128. package/lib/util/getTokenAddress.js +9 -0
  129. package/package.json +10 -3
  130. package/src/accounts/bulkAccountLoader.ts +44 -34
  131. package/src/accounts/bulkUserStatsSubscription.ts +33 -0
  132. package/src/accounts/bulkUserSubscription.ts +2 -3
  133. package/src/accounts/fetch.ts +27 -2
  134. package/src/accounts/{pollingClearingHouseAccountSubscriber.ts → pollingDriftClientAccountSubscriber.ts} +65 -75
  135. package/src/accounts/{pollingOracleSubscriber.ts → pollingOracleAccountSubscriber.ts} +2 -2
  136. package/src/accounts/pollingUserStatsAccountSubscriber.ts +172 -0
  137. package/src/accounts/types.ts +35 -15
  138. package/src/accounts/{webSocketClearingHouseAccountSubscriber.ts → webSocketDriftClientAccountSubscriber.ts} +78 -73
  139. package/src/accounts/webSocketUserStatsAccountSubsriber.ts +80 -0
  140. package/src/addresses/marketAddresses.ts +3 -4
  141. package/src/addresses/pda.ts +105 -33
  142. package/src/adminClient.ts +1207 -0
  143. package/src/config.ts +37 -31
  144. package/src/constants/numericConstants.ts +58 -24
  145. package/src/constants/{markets.ts → perpMarkets.ts} +10 -10
  146. package/src/constants/spotMarkets.ts +73 -0
  147. package/src/dlob/DLOB.ts +1123 -0
  148. package/src/dlob/DLOBNode.ts +155 -0
  149. package/src/dlob/NodeList.ts +195 -0
  150. package/src/driftClient.ts +3564 -0
  151. package/src/{clearingHouseConfig.ts → driftClientConfig.ts} +9 -8
  152. package/src/events/eventList.ts +3 -0
  153. package/src/events/eventSubscriber.ts +36 -14
  154. package/src/events/fetchLogs.ts +55 -13
  155. package/src/events/pollingLogProvider.ts +11 -3
  156. package/src/events/sort.ts +11 -15
  157. package/src/events/types.ts +21 -2
  158. package/src/events/webSocketLogProvider.ts +1 -1
  159. package/src/examples/makeTradeExample.ts +44 -28
  160. package/src/factory/bigNum.ts +150 -22
  161. package/src/idl/drift.json +8250 -0
  162. package/src/idl/pyth.json +98 -2
  163. package/src/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  164. package/src/index.ts +30 -13
  165. package/src/math/amm.ts +161 -48
  166. package/src/math/conversion.ts +2 -2
  167. package/src/math/exchangeStatus.ts +31 -0
  168. package/src/math/funding.ts +41 -31
  169. package/src/math/insurance.ts +35 -0
  170. package/src/math/margin.ts +133 -0
  171. package/src/math/market.ts +143 -14
  172. package/src/math/oracles.ts +63 -9
  173. package/src/math/orders.ts +163 -26
  174. package/src/math/position.ts +136 -58
  175. package/src/math/repeg.ts +19 -9
  176. package/src/math/spotBalance.ts +319 -0
  177. package/src/math/spotMarket.ts +9 -0
  178. package/src/math/spotPosition.ts +47 -0
  179. package/src/math/trade.ts +33 -37
  180. package/src/oracles/pythClient.ts +2 -2
  181. package/src/oracles/quoteAssetOracleClient.ts +2 -2
  182. package/src/oracles/switchboardClient.ts +2 -2
  183. package/src/orderParams.ts +16 -8
  184. package/src/serum/serumFulfillmentConfigMap.ts +26 -0
  185. package/src/serum/serumSubscriber.ts +99 -0
  186. package/src/serum/types.ts +13 -0
  187. package/src/slot/SlotSubscriber.ts +11 -1
  188. package/src/{mockUSDCFaucet.ts → tokenFaucet.ts} +82 -70
  189. package/src/tx/retryTxSender.ts +16 -5
  190. package/src/tx/types.ts +2 -1
  191. package/src/tx/utils.ts +1 -1
  192. package/src/types.ts +572 -178
  193. package/src/user.ts +1582 -0
  194. package/src/{clearingHouseUserConfig.ts → userConfig.ts} +5 -5
  195. package/src/userMap/userMap.ts +124 -0
  196. package/src/userMap/userStatsMap.ts +108 -0
  197. package/src/userName.ts +2 -1
  198. package/src/userStats.ts +75 -0
  199. package/src/userStatsConfig.ts +18 -0
  200. package/src/util/computeUnits.ts +1 -1
  201. package/src/util/getTokenAddress.ts +18 -0
  202. package/tests/bn/test.ts +46 -11
  203. package/tests/dlob/helpers.ts +611 -0
  204. package/tests/dlob/test.ts +4588 -0
  205. package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +0 -49
  206. package/lib/admin.d.ts +0 -44
  207. package/lib/admin.js +0 -433
  208. package/lib/clearingHouse.d.ts +0 -133
  209. package/lib/clearingHouse.js +0 -931
  210. package/lib/clearingHouseUser.d.ts +0 -187
  211. package/lib/clearingHouseUser.js +0 -643
  212. package/lib/clearingHouseUserConfig.d.ts +0 -14
  213. package/lib/constants/banks.d.ts +0 -16
  214. package/lib/constants/banks.js +0 -34
  215. package/lib/constants/markets.d.ts +0 -19
  216. package/lib/idl/clearing_house.json +0 -3998
  217. package/lib/math/bankBalance.d.ts +0 -9
  218. package/lib/math/bankBalance.js +0 -75
  219. package/lib/math/state.d.ts +0 -8
  220. package/lib/math/state.js +0 -15
  221. package/lib/orders.d.ts +0 -8
  222. package/lib/orders.js +0 -134
  223. package/src/admin.ts +0 -722
  224. package/src/clearingHouse.ts +0 -1451
  225. package/src/clearingHouseUser.ts +0 -989
  226. package/src/constants/banks.ts +0 -43
  227. package/src/idl/clearing_house.json +0 -3998
  228. package/src/math/bankBalance.ts +0 -112
  229. package/src/math/state.ts +0 -14
  230. package/src/math/utils.js +0 -27
  231. package/src/math/utils.js.map +0 -1
  232. package/src/orders.ts +0 -244
  233. package/src/util/computeUnits.js +0 -17
  234. package/src/util/computeUnits.js.map +0 -1
@@ -0,0 +1,4588 @@
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
+ Order,
20
+ isMarketOrder,
21
+ isLimitOrder,
22
+ ZERO,
23
+ } from '../../src';
24
+
25
+ import {
26
+ mockPerpMarkets,
27
+ mockSpotMarkets,
28
+ mockStateAccount,
29
+ MockUserMap,
30
+ } from './helpers';
31
+
32
+ function insertOrderToDLOB(
33
+ dlob: DLOB,
34
+ userAccount: PublicKey,
35
+ orderType: OrderType,
36
+ marketType: MarketType,
37
+ orderId: number,
38
+ marketIndex: number,
39
+ price: BN,
40
+ baseAssetAmount: BN,
41
+ direction: PositionDirection,
42
+ auctionStartPrice: BN,
43
+ auctionEndPrice: BN,
44
+ slot?: BN,
45
+ maxTs = ZERO,
46
+ oraclePriceOffset = new BN(0)
47
+ ) {
48
+ dlob.insertOrder(
49
+ {
50
+ status: OrderStatus.OPEN,
51
+ orderType,
52
+ marketType,
53
+ slot: slot || new BN(1),
54
+ orderId,
55
+ userOrderId: 0,
56
+ marketIndex,
57
+ price,
58
+ baseAssetAmount,
59
+ baseAssetAmountFilled: new BN(0),
60
+ quoteAssetAmount: new BN(0),
61
+ quoteAssetAmountFilled: new BN(0),
62
+ direction,
63
+ reduceOnly: false,
64
+ triggerPrice: new BN(0),
65
+ triggerCondition: OrderTriggerCondition.ABOVE,
66
+ triggered: false,
67
+ existingPositionDirection: PositionDirection.LONG,
68
+ postOnly: false,
69
+ immediateOrCancel: false,
70
+ oraclePriceOffset: oraclePriceOffset.toNumber(),
71
+ auctionDuration: 10,
72
+ auctionStartPrice,
73
+ auctionEndPrice,
74
+ maxTs,
75
+ },
76
+ userAccount
77
+ );
78
+ }
79
+
80
+ function insertTriggerOrderToDLOB(
81
+ dlob: DLOB,
82
+ userAccount: PublicKey,
83
+ orderType: OrderType,
84
+ marketType: MarketType,
85
+ orderId: number,
86
+ marketIndex: number,
87
+ price: BN,
88
+ baseAssetAmount: BN,
89
+ direction: PositionDirection,
90
+ triggerPrice: BN,
91
+ triggerCondition: OrderTriggerCondition,
92
+ auctionStartPrice: BN,
93
+ auctionEndPrice: BN,
94
+ slot?: BN,
95
+ maxTs = ZERO,
96
+ oraclePriceOffset = new BN(0)
97
+ ) {
98
+ dlob.insertOrder(
99
+ {
100
+ status: OrderStatus.OPEN,
101
+ orderType,
102
+ marketType,
103
+ slot: slot || new BN(1),
104
+ orderId,
105
+ userOrderId: 0,
106
+ marketIndex,
107
+ price,
108
+ baseAssetAmount,
109
+ baseAssetAmountFilled: new BN(0),
110
+ quoteAssetAmount: new BN(0),
111
+ quoteAssetAmountFilled: new BN(0),
112
+ direction,
113
+ reduceOnly: false,
114
+ triggerPrice,
115
+ triggerCondition,
116
+ triggered: false,
117
+ existingPositionDirection: PositionDirection.LONG,
118
+ postOnly: false,
119
+ immediateOrCancel: true,
120
+ oraclePriceOffset: oraclePriceOffset.toNumber(),
121
+ auctionDuration: 10,
122
+ auctionStartPrice,
123
+ auctionEndPrice,
124
+ maxTs,
125
+ },
126
+ userAccount
127
+ );
128
+ }
129
+
130
+ function printOrderNode(
131
+ node: DLOBNode,
132
+ oracle: OraclePriceData | undefined,
133
+ slot: number | undefined
134
+ ) {
135
+ console.log(
136
+ ` . vAMMNode? ${node.isVammNode()},\t${
137
+ node.order ? getVariant(node.order?.orderType) : '~'
138
+ } ${node.order ? getVariant(node.order?.direction) : '~'}\t, slot: ${
139
+ node.order?.slot.toString() || '~'
140
+ }, orderId: ${node.order?.orderId.toString() || '~'},\tnode.getPrice: ${
141
+ oracle ? node.getPrice(oracle, slot!) : '~'
142
+ }, node.price: ${node.order?.price.toString() || '~'}, priceOffset: ${
143
+ node.order?.oraclePriceOffset.toString() || '~'
144
+ } quantity: ${node.order?.baseAssetAmountFilled.toString() || '~'}/${
145
+ node.order?.baseAssetAmount.toString() || '~'
146
+ }`
147
+ );
148
+ }
149
+
150
+ function printBookState(
151
+ dlob: DLOB,
152
+ marketIndex: number,
153
+ vBid: BN | undefined,
154
+ vAsk: BN | undefined,
155
+ slot: number,
156
+ oracle: OraclePriceData
157
+ ) {
158
+ const askNodes = dlob.getAsks(
159
+ marketIndex,
160
+ vAsk,
161
+ slot,
162
+ MarketType.PERP,
163
+ oracle
164
+ );
165
+ let aa = 0;
166
+ console.log(`Oracle price: ${oracle.price.toNumber()}`);
167
+ console.log(`asks:`);
168
+ for (const a of askNodes) {
169
+ printOrderNode(a, oracle, slot);
170
+ aa++;
171
+ }
172
+ console.log(`Total ask nodes: ${aa}`);
173
+
174
+ const bidNodes = dlob.getBids(
175
+ marketIndex,
176
+ vBid,
177
+ slot,
178
+ MarketType.PERP,
179
+ oracle
180
+ );
181
+ let bb = 0;
182
+ console.log(`bids:`);
183
+ for (const b of bidNodes) {
184
+ printOrderNode(b, oracle, slot);
185
+ bb++;
186
+ }
187
+ console.log(`Total bid nodes: ${bb}`);
188
+ }
189
+
190
+ function printCrossedNodes(n: NodeToFill, slot: number) {
191
+ console.log(
192
+ `Cross Found, takerExists: ${n.node.order !== undefined}, makerExists: ${
193
+ n.makerNode !== undefined
194
+ }`
195
+ );
196
+ console.log(
197
+ `node: (mkt: ${isMarketOrder(n.node.order!)}, lim: ${isLimitOrder(
198
+ n.node.order!
199
+ )})`
200
+ );
201
+ if (n.makerNode) {
202
+ console.log(
203
+ `mkrnode: (mkt: ${isMarketOrder(n.makerNode.order!)}, lim: ${isLimitOrder(
204
+ n.makerNode.order!
205
+ )})`
206
+ );
207
+ }
208
+
209
+ const printOrder = (o: Order) => {
210
+ console.log(
211
+ ` orderId: ${o.orderId}, ${getVariant(o.orderType)}, ${getVariant(
212
+ o.direction
213
+ )},\texpired: ${isOrderExpired(o, slot)}, postOnly: ${
214
+ o.postOnly
215
+ }, reduceOnly: ${
216
+ o.reduceOnly
217
+ }, price: ${o.price.toString()}, priceOffset: ${o.oraclePriceOffset.toString()}, baseAmtFileld: ${o.baseAssetAmountFilled.toString()}/${o.baseAssetAmount.toString()}`
218
+ );
219
+ };
220
+
221
+ if (n.node.order) {
222
+ const t = n.node.order;
223
+ console.log(`Taker Order:`);
224
+ printOrder(t);
225
+ }
226
+ if (n.makerNode) {
227
+ if (n.makerNode.isVammNode()) {
228
+ console.log(` maker is vAMM node`);
229
+ } else {
230
+ const m = n.makerNode.order!;
231
+ console.log(`Maker Order:`);
232
+ printOrder(m);
233
+ }
234
+ }
235
+ }
236
+
237
+ describe('DLOB Tests', () => {
238
+ it('Fresh DLOB is empty', () => {
239
+ const mockUserMap = new MockUserMap();
240
+
241
+ const dlob = new DLOB(
242
+ mockPerpMarkets,
243
+ mockSpotMarkets,
244
+ mockStateAccount,
245
+ mockUserMap,
246
+ false
247
+ );
248
+ const vAsk = new BN(11);
249
+ const vBid = new BN(10);
250
+ const oracle = {
251
+ price: vBid.add(vAsk).div(new BN(2)),
252
+ slot: new BN(12),
253
+ confidence: new BN(1),
254
+ hasSufficientNumberOfDataPoints: true,
255
+ };
256
+
257
+ // check perps
258
+ for (const market of mockPerpMarkets) {
259
+ let foundAsks = 0;
260
+ for (const _ask of dlob.getAsks(
261
+ market.marketIndex,
262
+ vAsk,
263
+ 0,
264
+ MarketType.PERP,
265
+ oracle
266
+ )) {
267
+ foundAsks++;
268
+ }
269
+ expect(foundAsks).to.equal(1);
270
+
271
+ let foundBids = 0;
272
+ for (const _bid of dlob.getBids(
273
+ market.marketIndex,
274
+ vBid,
275
+ 0,
276
+ MarketType.PERP,
277
+ oracle
278
+ )) {
279
+ foundBids++;
280
+ }
281
+ expect(foundBids).to.equal(1);
282
+ }
283
+
284
+ // check spots
285
+ for (const market of mockSpotMarkets) {
286
+ let foundAsks = 0;
287
+ for (const _ask of dlob.getAsks(
288
+ market.marketIndex,
289
+ undefined,
290
+ 0,
291
+ MarketType.SPOT,
292
+ oracle
293
+ )) {
294
+ foundAsks++;
295
+ }
296
+ expect(foundAsks).to.equal(0);
297
+ let foundBids = 0;
298
+ for (const _bid of dlob.getBids(
299
+ market.marketIndex,
300
+ undefined,
301
+ 0,
302
+ MarketType.SPOT,
303
+ oracle
304
+ )) {
305
+ foundBids++;
306
+ }
307
+ expect(foundBids).to.equal(0);
308
+ }
309
+ });
310
+
311
+ it('Can clear DLOB', () => {
312
+ const vAsk = new BN(15);
313
+ const vBid = new BN(10);
314
+
315
+ const mockUserMap = new MockUserMap();
316
+ const user0 = Keypair.generate();
317
+ const user0Auth = Keypair.generate();
318
+ const user1 = Keypair.generate();
319
+ const user1Auth = Keypair.generate();
320
+ const user2 = Keypair.generate();
321
+ const user2Auth = Keypair.generate();
322
+ const user3 = Keypair.generate();
323
+ const user3Auth = Keypair.generate();
324
+ const user4 = Keypair.generate();
325
+ const user4Auth = Keypair.generate();
326
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
327
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user1Auth.publicKey);
328
+ mockUserMap.addUserAccountAuthority(user2.publicKey, user2Auth.publicKey);
329
+ mockUserMap.addUserAccountAuthority(user3.publicKey, user3Auth.publicKey);
330
+ mockUserMap.addUserAccountAuthority(user4.publicKey, user4Auth.publicKey);
331
+
332
+ const dlob = new DLOB(
333
+ mockPerpMarkets,
334
+ mockSpotMarkets,
335
+ mockStateAccount,
336
+ mockUserMap,
337
+ false
338
+ );
339
+ const marketIndex = 0;
340
+
341
+ const slot = 12;
342
+ const oracle = {
343
+ price: vBid.add(vAsk).div(new BN(2)),
344
+ slot: new BN(slot),
345
+ confidence: new BN(1),
346
+ hasSufficientNumberOfDataPoints: true,
347
+ };
348
+
349
+ insertOrderToDLOB(
350
+ dlob,
351
+ user0.publicKey,
352
+ OrderType.LIMIT,
353
+ MarketType.PERP,
354
+ 0, // orderId
355
+ marketIndex,
356
+ new BN(9), // price
357
+ BASE_PRECISION, // quantity
358
+ PositionDirection.LONG,
359
+ vBid,
360
+ vAsk
361
+ );
362
+ insertOrderToDLOB(
363
+ dlob,
364
+ user1.publicKey,
365
+ OrderType.LIMIT,
366
+ MarketType.PERP,
367
+ 1, // orderId
368
+ marketIndex,
369
+ new BN(8), // price
370
+ BASE_PRECISION, // quantity
371
+ PositionDirection.LONG,
372
+ vBid,
373
+ vAsk
374
+ );
375
+ insertOrderToDLOB(
376
+ dlob,
377
+ user2.publicKey,
378
+ OrderType.LIMIT,
379
+ MarketType.PERP,
380
+ 2, // orderId
381
+ marketIndex,
382
+ new BN(8), // price
383
+ BASE_PRECISION, // quantity
384
+ PositionDirection.LONG,
385
+ vBid,
386
+ vAsk
387
+ );
388
+
389
+ const bids = dlob.getBids(
390
+ marketIndex,
391
+ undefined,
392
+ 0,
393
+ MarketType.PERP,
394
+ oracle
395
+ );
396
+ let b = 0;
397
+ for (const _bid of bids) {
398
+ b++;
399
+ }
400
+ expect(b).to.equal(3);
401
+
402
+ dlob.clear();
403
+ let thrown = false;
404
+ try {
405
+ const bids1 = dlob.getBids(marketIndex, vBid, 0, MarketType.PERP, oracle);
406
+ bids1.next();
407
+ } catch (e) {
408
+ thrown = true;
409
+ }
410
+ expect(thrown, 'should throw after clearing').to.equal(true);
411
+ });
412
+ });
413
+
414
+ describe('DLOB Perp Tests', () => {
415
+ it('Test proper bids', () => {
416
+ const vAsk = new BN(15);
417
+ const vBid = new BN(10);
418
+ const mockUserMap = new MockUserMap();
419
+ const dlob = new DLOB(
420
+ mockPerpMarkets,
421
+ mockSpotMarkets,
422
+ mockStateAccount,
423
+ mockUserMap,
424
+ false
425
+ );
426
+ const marketIndex = 0;
427
+
428
+ const slot = 12;
429
+ const oracle = {
430
+ price: vBid.add(vAsk).div(new BN(2)),
431
+ slot: new BN(slot),
432
+ confidence: new BN(1),
433
+ hasSufficientNumberOfDataPoints: true,
434
+ };
435
+ const testCases = [
436
+ {
437
+ expectedIdx: 0,
438
+ isVamm: false,
439
+ orderId: 5,
440
+ price: new BN(0),
441
+ direction: PositionDirection.LONG,
442
+ orderType: OrderType.MARKET,
443
+ },
444
+ {
445
+ expectedIdx: 1,
446
+ isVamm: false,
447
+ orderId: 6,
448
+ price: new BN(0),
449
+ direction: PositionDirection.LONG,
450
+ orderType: OrderType.MARKET,
451
+ },
452
+ {
453
+ expectedIdx: 2,
454
+ isVamm: false,
455
+ orderId: 7,
456
+ price: new BN(0),
457
+ direction: PositionDirection.LONG,
458
+ orderType: OrderType.MARKET,
459
+ },
460
+ {
461
+ expectedIdx: 3,
462
+ isVamm: false,
463
+ orderId: 1,
464
+ price: new BN(12),
465
+ direction: PositionDirection.LONG,
466
+ orderType: OrderType.LIMIT,
467
+ },
468
+ {
469
+ expectedIdx: 4,
470
+ isVamm: false,
471
+ orderId: 2,
472
+ price: new BN(11),
473
+ direction: PositionDirection.LONG,
474
+ orderType: OrderType.LIMIT,
475
+ },
476
+ {
477
+ expectedIdx: 7,
478
+ isVamm: false,
479
+ orderId: 3,
480
+ price: new BN(8),
481
+ direction: PositionDirection.LONG,
482
+ orderType: OrderType.LIMIT,
483
+ },
484
+ {
485
+ expectedIdx: 5,
486
+ isVamm: true,
487
+ orderId: undefined,
488
+ price: undefined,
489
+ direction: undefined,
490
+ orderType: undefined,
491
+ },
492
+ {
493
+ expectedIdx: 6,
494
+ isVamm: false,
495
+ orderId: 4,
496
+ price: new BN(9),
497
+ direction: PositionDirection.LONG,
498
+ orderType: OrderType.LIMIT,
499
+ },
500
+ ];
501
+
502
+ for (const t of testCases) {
503
+ if (t.isVamm) {
504
+ continue;
505
+ }
506
+
507
+ const user = Keypair.generate();
508
+ const userAuth = Keypair.generate();
509
+ mockUserMap.addUserAccountAuthority(user.publicKey, userAuth.publicKey);
510
+
511
+ insertOrderToDLOB(
512
+ dlob,
513
+ user.publicKey,
514
+ t.orderType || OrderType.LIMIT,
515
+ MarketType.PERP,
516
+ t.orderId || 0, // orderId
517
+ marketIndex,
518
+ t.price || new BN(0), // price
519
+ BASE_PRECISION, // quantity
520
+ t.direction || PositionDirection.LONG,
521
+ vBid,
522
+ vAsk
523
+ );
524
+ }
525
+
526
+ const expectedTestCase = testCases.sort((a, b) => {
527
+ // ascending order
528
+ return a.expectedIdx - b.expectedIdx;
529
+ });
530
+ const bids = dlob.getBids(marketIndex, vBid, slot, MarketType.PERP, oracle);
531
+
532
+ console.log('The Book Bids:');
533
+ const gotBids: Array<DLOBNode> = [];
534
+ let countBids = 0;
535
+ for (const bid of bids) {
536
+ gotBids.push(bid);
537
+ printOrderNode(bid, oracle, slot);
538
+ }
539
+ for (const bid of gotBids) {
540
+ expect(bid.isVammNode(), `expected vAMM node`).to.be.eq(
541
+ expectedTestCase[countBids].isVamm
542
+ );
543
+ expect(bid.order?.orderId, `expected orderId`).to.equal(
544
+ expectedTestCase[countBids].orderId
545
+ );
546
+ expect(bid.order?.price.toNumber(), `expected price`).to.equal(
547
+ expectedTestCase[countBids].price?.toNumber()
548
+ );
549
+ expect(bid.order?.direction, `expected order direction`).to.equal(
550
+ expectedTestCase[countBids].direction
551
+ );
552
+ expect(bid.order?.orderType, `expected order type`).to.equal(
553
+ expectedTestCase[countBids].orderType
554
+ );
555
+ countBids++;
556
+ }
557
+
558
+ expect(countBids).to.equal(testCases.length);
559
+ });
560
+
561
+ it('Test proper bids on multiple markets', () => {
562
+ const vAsk = new BN(15);
563
+ const vBid = new BN(10);
564
+ const mockUserMap = new MockUserMap();
565
+ const dlob = new DLOB(
566
+ mockPerpMarkets,
567
+ mockSpotMarkets,
568
+ mockStateAccount,
569
+ mockUserMap,
570
+ false
571
+ );
572
+ const marketIndex0 = 0;
573
+ const marketIndex1 = 1;
574
+
575
+ const slot = 12;
576
+ const oracle = {
577
+ price: vBid.add(vAsk).div(new BN(2)),
578
+ slot: new BN(slot),
579
+ confidence: new BN(1),
580
+ hasSufficientNumberOfDataPoints: true,
581
+ };
582
+ const testCases = [
583
+ {
584
+ expectedIdx: 3,
585
+ isVamm: false,
586
+ orderId: 5,
587
+ price: new BN(0),
588
+ direction: PositionDirection.LONG,
589
+ orderType: OrderType.MARKET,
590
+ marketIndex: marketIndex0,
591
+ },
592
+ {
593
+ expectedIdx: 4,
594
+ isVamm: false,
595
+ orderId: 6,
596
+ price: new BN(0),
597
+ direction: PositionDirection.LONG,
598
+ orderType: OrderType.MARKET,
599
+ marketIndex: marketIndex0,
600
+ },
601
+ {
602
+ expectedIdx: 5,
603
+ isVamm: false,
604
+ orderId: 7,
605
+ price: new BN(0),
606
+ direction: PositionDirection.LONG,
607
+ orderType: OrderType.MARKET,
608
+ marketIndex: marketIndex1,
609
+ },
610
+ {
611
+ expectedIdx: 0,
612
+ isVamm: false,
613
+ orderId: 1,
614
+ price: new BN(12),
615
+ direction: PositionDirection.LONG,
616
+ orderType: OrderType.LIMIT,
617
+ marketIndex: marketIndex0,
618
+ },
619
+ {
620
+ expectedIdx: 1,
621
+ isVamm: false,
622
+ orderId: 2,
623
+ price: new BN(11),
624
+ direction: PositionDirection.LONG,
625
+ orderType: OrderType.LIMIT,
626
+ marketIndex: marketIndex0,
627
+ },
628
+ {
629
+ expectedIdx: 7,
630
+ isVamm: false,
631
+ orderId: 3,
632
+ price: new BN(8),
633
+ direction: PositionDirection.LONG,
634
+ orderType: OrderType.LIMIT,
635
+ marketIndex: marketIndex0,
636
+ },
637
+ {
638
+ expectedIdx: 6,
639
+ isVamm: false,
640
+ orderId: 4,
641
+ price: new BN(9),
642
+ direction: PositionDirection.LONG,
643
+ orderType: OrderType.LIMIT,
644
+ marketIndex: marketIndex1,
645
+ },
646
+ ];
647
+
648
+ for (const t of testCases) {
649
+ if (t.isVamm) {
650
+ continue;
651
+ }
652
+
653
+ const user = Keypair.generate();
654
+ const userAuth = Keypair.generate();
655
+ mockUserMap.addUserAccountAuthority(user.publicKey, userAuth.publicKey);
656
+
657
+ insertOrderToDLOB(
658
+ dlob,
659
+ user.publicKey,
660
+ t.orderType || OrderType.LIMIT,
661
+ MarketType.PERP,
662
+ t.orderId || 0, // orderId
663
+ t.marketIndex,
664
+ t.price || new BN(0), // price
665
+ BASE_PRECISION, // quantity
666
+ t.direction || PositionDirection.LONG,
667
+ vBid,
668
+ vAsk
669
+ );
670
+ }
671
+
672
+ const bids0 = dlob.getBids(
673
+ marketIndex0,
674
+ vBid,
675
+ slot,
676
+ MarketType.PERP,
677
+ oracle
678
+ );
679
+ let countBids0 = 0;
680
+ for (const bid of bids0) {
681
+ console.log(
682
+ ` . vAMMNode? ${bid.isVammNode()}, ${JSON.stringify(
683
+ bid.order?.orderType
684
+ )} , ${bid.order?.orderId.toString()} , vammTestgetPRice: ${bid.getPrice(
685
+ oracle,
686
+ slot
687
+ )}, price: ${bid.order?.price.toString()}, quantity: ${bid.order?.baseAssetAmountFilled.toString()}/${bid.order?.baseAssetAmount.toString()}`
688
+ );
689
+ countBids0++;
690
+ }
691
+ expect(countBids0).to.equal(6);
692
+
693
+ const bids1 = dlob.getBids(
694
+ marketIndex1,
695
+ vBid,
696
+ slot,
697
+ MarketType.PERP,
698
+ oracle
699
+ );
700
+ let countBids1 = 0;
701
+ for (const bid of bids1) {
702
+ console.log(
703
+ ` . vAMMNode? ${bid.isVammNode()}, ${JSON.stringify(
704
+ bid.order?.orderType
705
+ )} , ${bid.order?.orderId.toString()} , vammTestgetPRice: ${bid.getPrice(
706
+ oracle,
707
+ slot
708
+ )}, price: ${bid.order?.price.toString()}, quantity: ${bid.order?.baseAssetAmountFilled.toString()}/${bid.order?.baseAssetAmount.toString()}`
709
+ );
710
+
711
+ countBids1++;
712
+ }
713
+ expect(countBids1).to.equal(3);
714
+ });
715
+
716
+ it('Test proper asks', () => {
717
+ const vAsk = new BN(15);
718
+ const vBid = new BN(10);
719
+ const mockUserMap = new MockUserMap();
720
+ const dlob = new DLOB(
721
+ mockPerpMarkets,
722
+ mockSpotMarkets,
723
+ mockStateAccount,
724
+ mockUserMap,
725
+ false
726
+ );
727
+ const marketIndex = 0;
728
+
729
+ const slot = 12;
730
+ const oracle = {
731
+ price: vBid.add(vAsk).div(new BN(2)),
732
+ slot: new BN(slot),
733
+ confidence: new BN(1),
734
+ hasSufficientNumberOfDataPoints: true,
735
+ };
736
+ const testCases = [
737
+ {
738
+ expectedIdx: 0,
739
+ isVamm: false,
740
+ orderId: 3,
741
+ price: new BN(0),
742
+ direction: PositionDirection.SHORT,
743
+ orderType: OrderType.MARKET,
744
+ },
745
+ {
746
+ expectedIdx: 1,
747
+ isVamm: false,
748
+ orderId: 4,
749
+ price: new BN(0),
750
+ direction: PositionDirection.SHORT,
751
+ orderType: OrderType.MARKET,
752
+ },
753
+ {
754
+ expectedIdx: 2,
755
+ isVamm: false,
756
+ orderId: 5,
757
+ price: new BN(0),
758
+ direction: PositionDirection.SHORT,
759
+ orderType: OrderType.MARKET,
760
+ },
761
+ {
762
+ expectedIdx: 3,
763
+ isVamm: false,
764
+ orderId: 1,
765
+ price: new BN(13),
766
+ direction: PositionDirection.SHORT,
767
+ orderType: OrderType.LIMIT,
768
+ },
769
+ {
770
+ expectedIdx: 6,
771
+ isVamm: false,
772
+ orderId: 6,
773
+ price: new BN(16),
774
+ direction: PositionDirection.SHORT,
775
+ orderType: OrderType.LIMIT,
776
+ },
777
+ {
778
+ expectedIdx: 5,
779
+ isVamm: true,
780
+ orderId: undefined,
781
+ price: undefined,
782
+ direction: undefined,
783
+ orderType: undefined,
784
+ },
785
+ {
786
+ expectedIdx: 7,
787
+ isVamm: false,
788
+ orderId: 7,
789
+ price: new BN(17),
790
+ direction: PositionDirection.SHORT,
791
+ orderType: OrderType.LIMIT,
792
+ },
793
+ {
794
+ expectedIdx: 4,
795
+ isVamm: false,
796
+ orderId: 2,
797
+ price: new BN(14),
798
+ direction: PositionDirection.SHORT,
799
+ orderType: OrderType.LIMIT,
800
+ },
801
+ ];
802
+
803
+ for (const t of testCases) {
804
+ if (t.isVamm) {
805
+ continue;
806
+ }
807
+
808
+ const user = Keypair.generate();
809
+ const userAuth = Keypair.generate();
810
+ mockUserMap.addUserAccountAuthority(user.publicKey, userAuth.publicKey);
811
+
812
+ insertOrderToDLOB(
813
+ dlob,
814
+ user.publicKey,
815
+ t.orderType || OrderType.LIMIT,
816
+ MarketType.PERP,
817
+ t.orderId || 0, // orderId
818
+ marketIndex,
819
+ t.price || new BN(0), // price
820
+ BASE_PRECISION, // quantity
821
+ t.direction || PositionDirection.SHORT,
822
+ vBid,
823
+ vAsk
824
+ );
825
+ }
826
+
827
+ const expectedTestCase = testCases.sort((a, b) => {
828
+ // ascending order
829
+ return a.expectedIdx - b.expectedIdx;
830
+ });
831
+ const asks = dlob.getAsks(marketIndex, vAsk, slot, MarketType.PERP, oracle);
832
+
833
+ console.log('The Book Asks:');
834
+ let countAsks = 0;
835
+ for (const ask of asks) {
836
+ console.log(
837
+ ` . vAMMNode? ${ask.isVammNode()}, ${JSON.stringify(
838
+ ask.order?.orderType
839
+ )} , ${ask.order?.orderId.toString()} , vammTestgetPRice: ${ask.getPrice(
840
+ oracle,
841
+ slot
842
+ )}, price: ${ask.order?.price.toString()}, quantity: ${ask.order?.baseAssetAmountFilled.toString()}/${ask.order?.baseAssetAmount.toString()}`
843
+ );
844
+
845
+ expect(ask.isVammNode()).to.be.eq(expectedTestCase[countAsks].isVamm);
846
+ expect(ask.order?.orderId).to.equal(expectedTestCase[countAsks].orderId);
847
+ expect(ask.order?.price.toNumber()).to.equal(
848
+ expectedTestCase[countAsks].price?.toNumber()
849
+ );
850
+ expect(ask.order?.direction).to.equal(
851
+ expectedTestCase[countAsks].direction
852
+ );
853
+ expect(ask.order?.orderType).to.equal(
854
+ expectedTestCase[countAsks].orderType
855
+ );
856
+ countAsks++;
857
+ }
858
+
859
+ expect(countAsks).to.equal(testCases.length);
860
+ });
861
+
862
+ it('Test insert market orders', () => {
863
+ const vAsk = new BN(11);
864
+ const vBid = new BN(10);
865
+ const mockUserMap = new MockUserMap();
866
+ const dlob = new DLOB(
867
+ mockPerpMarkets,
868
+ mockSpotMarkets,
869
+ mockStateAccount,
870
+ mockUserMap,
871
+ false
872
+ );
873
+ const marketIndex = 0;
874
+ const oracle = {
875
+ price: vBid.add(vAsk).div(new BN(2)),
876
+ slot: new BN(12),
877
+ confidence: new BN(1),
878
+ hasSufficientNumberOfDataPoints: true,
879
+ };
880
+
881
+ // 3 mkt buys
882
+ for (let i = 0; i < 3; i++) {
883
+ const user = Keypair.generate();
884
+ const userAuth = Keypair.generate();
885
+ mockUserMap.addUserAccountAuthority(user.publicKey, userAuth.publicKey);
886
+
887
+ insertOrderToDLOB(
888
+ dlob,
889
+ user.publicKey,
890
+ OrderType.MARKET,
891
+ MarketType.PERP,
892
+ i + 1,
893
+ marketIndex,
894
+ new BN(0),
895
+ BASE_PRECISION,
896
+ PositionDirection.LONG,
897
+ vBid,
898
+ vAsk
899
+ );
900
+ }
901
+
902
+ // 3 mkt sells
903
+ for (let i = 0; i < 3; i++) {
904
+ const user = Keypair.generate();
905
+ const userAuth = Keypair.generate();
906
+ mockUserMap.addUserAccountAuthority(user.publicKey, userAuth.publicKey);
907
+
908
+ insertOrderToDLOB(
909
+ dlob,
910
+ user.publicKey,
911
+ OrderType.MARKET,
912
+ MarketType.PERP,
913
+ i + 1,
914
+ marketIndex,
915
+ new BN(0),
916
+ BASE_PRECISION,
917
+ PositionDirection.SHORT,
918
+ vBid,
919
+ vAsk
920
+ );
921
+ }
922
+
923
+ let asks = 0;
924
+ for (const ask of dlob.getAsks(
925
+ marketIndex,
926
+ vAsk,
927
+ 2,
928
+ MarketType.PERP,
929
+ oracle
930
+ )) {
931
+ // vamm node is last in asks
932
+ asks++;
933
+
934
+ if (ask.order) {
935
+ // market orders
936
+ expect(getVariant(ask.order?.status)).to.equal('open');
937
+ expect(getVariant(ask.order?.orderType)).to.equal('market');
938
+ expect(getVariant(ask.order?.direction)).to.equal('short');
939
+ expect(ask.order?.orderId).to.equal(asks);
940
+ }
941
+ }
942
+ expect(asks).to.equal(4); // vamm ask + 3 orders
943
+
944
+ let bids = 0;
945
+ for (const bid of dlob.getBids(
946
+ marketIndex,
947
+ vBid,
948
+ 2,
949
+ MarketType.PERP,
950
+ oracle
951
+ )) {
952
+ if (bids === 0) {
953
+ // vamm node
954
+ expect(bid.order).to.equal(undefined);
955
+ } else {
956
+ // market orders
957
+ expect(getVariant(bid.order?.status)).to.equal('open');
958
+ expect(getVariant(bid.order?.orderType)).to.equal('market');
959
+ expect(getVariant(bid.order?.direction)).to.equal('long');
960
+ expect(bid.order?.orderId).to.equal(bids);
961
+ }
962
+ bids++;
963
+ }
964
+ expect(bids).to.equal(4); // vamm bid + 3 orders
965
+ });
966
+
967
+ it('Test insert limit orders', () => {
968
+ const slot = 12;
969
+ const vAsk = new BN(11);
970
+ const vBid = new BN(10);
971
+ const oracle = {
972
+ price: vBid.add(vAsk).div(new BN(2)),
973
+ slot: new BN(slot),
974
+ confidence: new BN(1),
975
+ hasSufficientNumberOfDataPoints: true,
976
+ };
977
+
978
+ const mockUserMap = new MockUserMap();
979
+ const user0 = Keypair.generate();
980
+ const userAuth0 = Keypair.generate();
981
+ const user1 = Keypair.generate();
982
+ const userAuth1 = Keypair.generate();
983
+ const user2 = Keypair.generate();
984
+ const userAuth2 = Keypair.generate();
985
+ const user3 = Keypair.generate();
986
+ const userAuth3 = Keypair.generate();
987
+ const user4 = Keypair.generate();
988
+ const userAuth4 = Keypair.generate();
989
+ const user5 = Keypair.generate();
990
+ const userAuth5 = Keypair.generate();
991
+
992
+ mockUserMap.addUserAccountAuthority(user0.publicKey, userAuth0.publicKey);
993
+ mockUserMap.addUserAccountAuthority(user1.publicKey, userAuth1.publicKey);
994
+ mockUserMap.addUserAccountAuthority(user2.publicKey, userAuth2.publicKey);
995
+ mockUserMap.addUserAccountAuthority(user3.publicKey, userAuth3.publicKey);
996
+ mockUserMap.addUserAccountAuthority(user4.publicKey, userAuth4.publicKey);
997
+ mockUserMap.addUserAccountAuthority(user5.publicKey, userAuth5.publicKey);
998
+
999
+ const dlob = new DLOB(
1000
+ mockPerpMarkets,
1001
+ mockSpotMarkets,
1002
+ mockStateAccount,
1003
+ mockUserMap,
1004
+ false
1005
+ );
1006
+ const marketIndex = 0;
1007
+ insertOrderToDLOB(
1008
+ dlob,
1009
+ user0.publicKey,
1010
+ OrderType.LIMIT,
1011
+ MarketType.PERP,
1012
+ 3, // orderId
1013
+ marketIndex,
1014
+ new BN(5),
1015
+ BASE_PRECISION,
1016
+ PositionDirection.LONG,
1017
+ vBid,
1018
+ vAsk
1019
+ );
1020
+
1021
+ insertOrderToDLOB(
1022
+ dlob,
1023
+ user1.publicKey,
1024
+ OrderType.LIMIT,
1025
+ MarketType.PERP,
1026
+ 2,
1027
+ marketIndex,
1028
+ new BN(6),
1029
+ BASE_PRECISION,
1030
+ PositionDirection.LONG,
1031
+ vBid,
1032
+ vAsk
1033
+ );
1034
+
1035
+ insertOrderToDLOB(
1036
+ dlob,
1037
+ user2.publicKey,
1038
+ OrderType.LIMIT,
1039
+ MarketType.PERP,
1040
+ 1, // orderId
1041
+ marketIndex,
1042
+ new BN(7),
1043
+ BASE_PRECISION,
1044
+ PositionDirection.LONG,
1045
+ vBid,
1046
+ vAsk
1047
+ );
1048
+
1049
+ insertOrderToDLOB(
1050
+ dlob,
1051
+ user3.publicKey,
1052
+ OrderType.LIMIT,
1053
+ MarketType.PERP,
1054
+ 1, // orderId
1055
+ marketIndex,
1056
+ new BN(12),
1057
+ BASE_PRECISION,
1058
+ PositionDirection.SHORT,
1059
+ vBid,
1060
+ vAsk
1061
+ );
1062
+
1063
+ insertOrderToDLOB(
1064
+ dlob,
1065
+ user4.publicKey,
1066
+ OrderType.LIMIT,
1067
+ MarketType.PERP,
1068
+ 2, // orderId
1069
+ marketIndex,
1070
+ new BN(13),
1071
+ BASE_PRECISION,
1072
+ PositionDirection.SHORT,
1073
+ vBid,
1074
+ vAsk
1075
+ );
1076
+
1077
+ insertOrderToDLOB(
1078
+ dlob,
1079
+ user5.publicKey,
1080
+ OrderType.LIMIT,
1081
+ MarketType.PERP,
1082
+ 3, // orderId
1083
+ marketIndex,
1084
+ new BN(14),
1085
+ BASE_PRECISION,
1086
+ PositionDirection.SHORT,
1087
+ vBid,
1088
+ vAsk
1089
+ );
1090
+
1091
+ let asks = 0;
1092
+ for (const ask of dlob.getAsks(
1093
+ marketIndex,
1094
+ vAsk,
1095
+ 2,
1096
+ MarketType.PERP,
1097
+ oracle
1098
+ )) {
1099
+ if (ask.order) {
1100
+ // market orders
1101
+ console.log(`ask price: ${ask.order.price.toString()}`);
1102
+ expect(getVariant(ask.order?.status)).to.equal('open');
1103
+ expect(getVariant(ask.order?.orderType)).to.equal('limit');
1104
+ expect(getVariant(ask.order?.direction)).to.equal('short');
1105
+ expect(ask.order?.orderId).to.equal(asks);
1106
+ expect(ask.order?.price.gt(vAsk)).to.equal(true);
1107
+ }
1108
+
1109
+ // vamm node is first for limit asks
1110
+ asks++;
1111
+ }
1112
+ expect(asks).to.equal(4); // vamm ask + 3 orders
1113
+
1114
+ let bids = 0;
1115
+ for (const bid of dlob.getBids(
1116
+ marketIndex,
1117
+ vBid,
1118
+ 2,
1119
+ MarketType.PERP,
1120
+ oracle
1121
+ )) {
1122
+ if (bids === 0) {
1123
+ // vamm node
1124
+ expect(bid.order).to.equal(undefined);
1125
+ } else {
1126
+ // market orders
1127
+ console.log(`bid price: ${bid.order?.price.toString()}`);
1128
+ expect(getVariant(bid.order?.status)).to.equal('open');
1129
+ expect(getVariant(bid.order?.orderType)).to.equal('limit');
1130
+ expect(getVariant(bid.order?.direction)).to.equal('long');
1131
+ expect(bid.order?.orderId).to.equal(bids);
1132
+ expect(bid.order?.price.lt(vBid)).to.equal(true);
1133
+ }
1134
+ bids++;
1135
+ }
1136
+ expect(bids).to.equal(4); // vamm bid + 3 orders
1137
+ });
1138
+
1139
+ it('Test insert floatinglimit orders', () => {
1140
+ const slot = 12;
1141
+ const vAsk = new BN(11).mul(PRICE_PRECISION);
1142
+ const vBid = new BN(10).mul(PRICE_PRECISION);
1143
+ const oracle = {
1144
+ price: vBid.add(vAsk).div(new BN(2)),
1145
+ slot: new BN(slot),
1146
+ confidence: new BN(1),
1147
+ hasSufficientNumberOfDataPoints: true,
1148
+ };
1149
+
1150
+ const mockUserMap = new MockUserMap();
1151
+ const user0 = Keypair.generate();
1152
+ const userAuth0 = Keypair.generate();
1153
+ const user1 = Keypair.generate();
1154
+ const userAuth1 = Keypair.generate();
1155
+ const user2 = Keypair.generate();
1156
+ const userAuth2 = Keypair.generate();
1157
+ const user3 = Keypair.generate();
1158
+ const userAuth3 = Keypair.generate();
1159
+ const user4 = Keypair.generate();
1160
+ const userAuth4 = Keypair.generate();
1161
+ const user5 = Keypair.generate();
1162
+ const userAuth5 = Keypair.generate();
1163
+
1164
+ mockUserMap.addUserAccountAuthority(user0.publicKey, userAuth0.publicKey);
1165
+ mockUserMap.addUserAccountAuthority(user1.publicKey, userAuth1.publicKey);
1166
+ mockUserMap.addUserAccountAuthority(user2.publicKey, userAuth2.publicKey);
1167
+ mockUserMap.addUserAccountAuthority(user3.publicKey, userAuth3.publicKey);
1168
+ mockUserMap.addUserAccountAuthority(user4.publicKey, userAuth4.publicKey);
1169
+ mockUserMap.addUserAccountAuthority(user5.publicKey, userAuth5.publicKey);
1170
+
1171
+ const dlob = new DLOB(
1172
+ mockPerpMarkets,
1173
+ mockSpotMarkets,
1174
+ mockStateAccount,
1175
+ mockUserMap,
1176
+ false
1177
+ );
1178
+ const marketIndex = 0;
1179
+
1180
+ // insert floating bids
1181
+ insertOrderToDLOB(
1182
+ dlob,
1183
+ user0.publicKey,
1184
+ OrderType.LIMIT,
1185
+ MarketType.PERP,
1186
+ 1, // orderId
1187
+ marketIndex,
1188
+ new BN(0), // price
1189
+ BASE_PRECISION, // quantity
1190
+ PositionDirection.LONG,
1191
+ vBid,
1192
+ vAsk,
1193
+ new BN(slot),
1194
+ ZERO, // TiF
1195
+ new BN(-1).mul(PRICE_PRECISION) // oraclePriceOffset
1196
+ );
1197
+ insertOrderToDLOB(
1198
+ dlob,
1199
+ user1.publicKey,
1200
+ OrderType.LIMIT,
1201
+ MarketType.PERP,
1202
+ 3, // orderId
1203
+ marketIndex,
1204
+ new BN(0), // price
1205
+ BASE_PRECISION, // quantity
1206
+ PositionDirection.LONG,
1207
+ vBid,
1208
+ vAsk,
1209
+ new BN(slot),
1210
+ ZERO, // TiF
1211
+ new BN(-3).mul(PRICE_PRECISION) // oraclePriceOffset
1212
+ );
1213
+ insertOrderToDLOB(
1214
+ dlob,
1215
+ user2.publicKey,
1216
+ OrderType.LIMIT,
1217
+ MarketType.PERP,
1218
+ 2, // orderId
1219
+ marketIndex,
1220
+ new BN(0), // price
1221
+ BASE_PRECISION, // quantity
1222
+ PositionDirection.LONG,
1223
+ vBid,
1224
+ vAsk,
1225
+ new BN(slot),
1226
+ ZERO, // TiF
1227
+ new BN(-2).mul(PRICE_PRECISION) // oraclePriceOffset
1228
+ );
1229
+
1230
+ // insert floating asks
1231
+ insertOrderToDLOB(
1232
+ dlob,
1233
+ user3.publicKey,
1234
+ OrderType.LIMIT,
1235
+ MarketType.PERP,
1236
+ 5, // orderId
1237
+ marketIndex,
1238
+ new BN(0), // price
1239
+ BASE_PRECISION, // quantity
1240
+ PositionDirection.SHORT,
1241
+ vAsk,
1242
+ vBid,
1243
+ new BN(slot),
1244
+ ZERO, // TiF
1245
+ new BN(2).mul(PRICE_PRECISION) // oraclePriceOffset
1246
+ );
1247
+ insertOrderToDLOB(
1248
+ dlob,
1249
+ user4.publicKey,
1250
+ OrderType.LIMIT,
1251
+ MarketType.PERP,
1252
+ 6, // orderId
1253
+ marketIndex,
1254
+ new BN(0), // price
1255
+ BASE_PRECISION, // quantity
1256
+ PositionDirection.SHORT,
1257
+ vAsk,
1258
+ vBid,
1259
+ new BN(slot),
1260
+ ZERO, // TiF
1261
+ new BN(3).mul(PRICE_PRECISION) // oraclePriceOffset
1262
+ );
1263
+ insertOrderToDLOB(
1264
+ dlob,
1265
+ user5.publicKey,
1266
+ OrderType.LIMIT,
1267
+ MarketType.PERP,
1268
+ 4, // orderId
1269
+ marketIndex,
1270
+ new BN(0), // price
1271
+ BASE_PRECISION, // quantity
1272
+ PositionDirection.SHORT,
1273
+ vAsk,
1274
+ vBid,
1275
+ new BN(slot),
1276
+ ZERO, // TiF
1277
+ new BN(1).mul(PRICE_PRECISION) // oraclePriceOffset
1278
+ );
1279
+
1280
+ // check floating bids
1281
+ console.log(`bids:`);
1282
+ let lastBidPrice = new BN(9999999999999); // very big
1283
+ let bids = 0;
1284
+ for (const bid of dlob.getBids(
1285
+ marketIndex,
1286
+ vBid,
1287
+ slot,
1288
+ MarketType.PERP,
1289
+ oracle
1290
+ )) {
1291
+ printOrderNode(bid, oracle, slot);
1292
+
1293
+ if (!bid.isVammNode()) {
1294
+ expect(getVariant(bid.order?.status)).to.equal('open');
1295
+ expect(getVariant(bid.order?.orderType)).to.equal('limit');
1296
+ expect(getVariant(bid.order?.direction)).to.equal('long');
1297
+
1298
+ // price should be getting worse (getting lower) as we iterate
1299
+ const currentPrice = bid.getPrice(oracle, slot);
1300
+ expect(
1301
+ currentPrice.lte(lastBidPrice),
1302
+ `each bid should be lte the last. current: ${currentPrice.toString()}, last: ${lastBidPrice.toString()}`
1303
+ ).to.be.true;
1304
+ }
1305
+ lastBidPrice = bid.getPrice(oracle, slot);
1306
+ bids++;
1307
+ }
1308
+ expect(bids).to.equal(4); // vamm bid + 3 orders
1309
+
1310
+ // check floating asks
1311
+ console.log(`asks:`);
1312
+ let asks = 0;
1313
+ for (const ask of dlob.getAsks(
1314
+ marketIndex,
1315
+ vAsk,
1316
+ slot,
1317
+ MarketType.PERP,
1318
+ oracle
1319
+ )) {
1320
+ printOrderNode(ask, oracle, slot);
1321
+ if (!ask.isVammNode()) {
1322
+ expect(getVariant(ask.order?.status)).to.equal('open');
1323
+ expect(getVariant(ask.order?.orderType)).to.equal('limit');
1324
+ expect(getVariant(ask.order?.direction)).to.equal('short');
1325
+
1326
+ // price should be getting worse (getting higher) as we iterate
1327
+ const currentPrice = ask.getPrice(oracle, slot);
1328
+ expect(
1329
+ currentPrice.gte(lastBidPrice),
1330
+ `each ask should be gte the last. current: ${currentPrice.toString()}, last: ${lastBidPrice.toString()}`
1331
+ ).to.be.true;
1332
+ }
1333
+ asks++;
1334
+ }
1335
+ expect(asks).to.equal(4); // vamm ask + 3 orders
1336
+ });
1337
+
1338
+ it('Test multiple market orders fill with multiple limit orders', async () => {
1339
+ const vAsk = new BN(15);
1340
+ const vBid = new BN(10);
1341
+
1342
+ const mockUserMap = new MockUserMap();
1343
+ const user0 = Keypair.generate();
1344
+ const user0Auth = Keypair.generate();
1345
+ const user1 = Keypair.generate();
1346
+ const user1Auth = Keypair.generate();
1347
+ const user2 = Keypair.generate();
1348
+ const user2Auth = Keypair.generate();
1349
+ const user3 = Keypair.generate();
1350
+ const user3Auth = Keypair.generate();
1351
+ const user4 = Keypair.generate();
1352
+ const user4Auth = Keypair.generate();
1353
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
1354
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user1Auth.publicKey);
1355
+ mockUserMap.addUserAccountAuthority(user2.publicKey, user2Auth.publicKey);
1356
+ mockUserMap.addUserAccountAuthority(user3.publicKey, user3Auth.publicKey);
1357
+ mockUserMap.addUserAccountAuthority(user4.publicKey, user4Auth.publicKey);
1358
+
1359
+ const dlob = new DLOB(
1360
+ mockPerpMarkets,
1361
+ mockSpotMarkets,
1362
+ mockStateAccount,
1363
+ mockUserMap,
1364
+ false
1365
+ );
1366
+ const marketIndex = 0;
1367
+
1368
+ // insert some limit buys above vamm bid, below ask
1369
+ insertOrderToDLOB(
1370
+ dlob,
1371
+ user0.publicKey,
1372
+ OrderType.LIMIT,
1373
+ MarketType.PERP,
1374
+ 1, // orderId
1375
+ marketIndex,
1376
+ new BN(11), // price
1377
+ BASE_PRECISION, // quantity
1378
+ PositionDirection.LONG,
1379
+ vBid,
1380
+ vAsk
1381
+ );
1382
+ insertOrderToDLOB(
1383
+ dlob,
1384
+ user1.publicKey,
1385
+ OrderType.LIMIT,
1386
+ MarketType.PERP,
1387
+ 2, // orderId
1388
+ marketIndex,
1389
+ new BN(12), // price
1390
+ BASE_PRECISION, // quantity
1391
+ PositionDirection.LONG,
1392
+ vBid,
1393
+ vAsk
1394
+ );
1395
+ insertOrderToDLOB(
1396
+ dlob,
1397
+ user2.publicKey,
1398
+ OrderType.LIMIT,
1399
+ MarketType.PERP,
1400
+ 3, // orderId
1401
+ marketIndex,
1402
+ new BN(13), // price
1403
+ BASE_PRECISION, // quantity
1404
+ PositionDirection.LONG,
1405
+ vBid,
1406
+ vAsk
1407
+ );
1408
+
1409
+ // should have no crossing orders
1410
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
1411
+ marketIndex,
1412
+ 12, // auction over
1413
+ MarketType.PERP,
1414
+ {
1415
+ price: vBid.add(vAsk).div(new BN(2)),
1416
+ slot: new BN(12),
1417
+ confidence: new BN(1),
1418
+ hasSufficientNumberOfDataPoints: true,
1419
+ }
1420
+ );
1421
+ expect(nodesToFillBefore.length).to.equal(0);
1422
+
1423
+ // place two market sell order eating 2 of the limit orders
1424
+ insertOrderToDLOB(
1425
+ dlob,
1426
+ user3.publicKey,
1427
+ OrderType.MARKET,
1428
+ MarketType.PERP,
1429
+ 4, // orderId
1430
+ marketIndex,
1431
+ new BN(12), // price
1432
+ new BN(1).mul(BASE_PRECISION), // quantity
1433
+ PositionDirection.SHORT,
1434
+ vBid,
1435
+ vAsk
1436
+ );
1437
+ insertOrderToDLOB(
1438
+ dlob,
1439
+ user4.publicKey,
1440
+ OrderType.MARKET,
1441
+ MarketType.PERP,
1442
+ 5, // orderId
1443
+ marketIndex,
1444
+ new BN(12), // price
1445
+ new BN(1).mul(BASE_PRECISION), // quantity
1446
+ PositionDirection.SHORT,
1447
+ vBid,
1448
+ vAsk
1449
+ );
1450
+
1451
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
1452
+ marketIndex,
1453
+ 12, // auction over
1454
+ MarketType.PERP,
1455
+ {
1456
+ price: vBid.add(vAsk).div(new BN(2)),
1457
+ slot: new BN(12),
1458
+ confidence: new BN(1),
1459
+ hasSufficientNumberOfDataPoints: true,
1460
+ }
1461
+ );
1462
+ console.log(`Filled nodes: ${nodesToFillAfter.length}`);
1463
+ for (const n of nodesToFillAfter) {
1464
+ printCrossedNodes(n, 12);
1465
+ }
1466
+ expect(nodesToFillAfter.length).to.equal(2);
1467
+
1468
+ // first taker should fill with best maker
1469
+ expect(nodesToFillAfter[0].node.order?.orderId).to.equal(4);
1470
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId).to.equal(3);
1471
+
1472
+ // second taker should fill with second best maker
1473
+ expect(nodesToFillAfter[1].node.order?.orderId).to.equal(5);
1474
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId).to.equal(2);
1475
+ });
1476
+
1477
+ it('Test one market orders fills two limit orders', () => {
1478
+ const vAsk = new BN(15);
1479
+ const vBid = new BN(10);
1480
+
1481
+ const mockUserMap = new MockUserMap();
1482
+ const user0 = Keypair.generate();
1483
+ const user0Auth = Keypair.generate();
1484
+ const user1 = Keypair.generate();
1485
+ const user1Auth = Keypair.generate();
1486
+ const user2 = Keypair.generate();
1487
+ const user2Auth = Keypair.generate();
1488
+ const user3 = Keypair.generate();
1489
+ const user3Auth = Keypair.generate();
1490
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
1491
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user1Auth.publicKey);
1492
+ mockUserMap.addUserAccountAuthority(user2.publicKey, user2Auth.publicKey);
1493
+ mockUserMap.addUserAccountAuthority(user3.publicKey, user3Auth.publicKey);
1494
+
1495
+ const dlob = new DLOB(
1496
+ mockPerpMarkets,
1497
+ mockSpotMarkets,
1498
+ mockStateAccount,
1499
+ mockUserMap,
1500
+ false
1501
+ );
1502
+ const marketIndex = 0;
1503
+
1504
+ // insert some limit sells below vAMM ask, above bid
1505
+ insertOrderToDLOB(
1506
+ dlob,
1507
+ user0.publicKey,
1508
+ OrderType.LIMIT,
1509
+ MarketType.PERP,
1510
+ 1, // orderId
1511
+ marketIndex,
1512
+ new BN(14), // price
1513
+ BASE_PRECISION, // quantity
1514
+ PositionDirection.SHORT,
1515
+ vBid,
1516
+ vAsk
1517
+ );
1518
+ insertOrderToDLOB(
1519
+ dlob,
1520
+ user1.publicKey,
1521
+ OrderType.LIMIT,
1522
+ MarketType.PERP,
1523
+ 2, // orderId
1524
+ marketIndex,
1525
+ new BN(13), // price
1526
+ BASE_PRECISION, // quantity
1527
+ PositionDirection.SHORT,
1528
+ vBid,
1529
+ vAsk
1530
+ );
1531
+ insertOrderToDLOB(
1532
+ dlob,
1533
+ user2.publicKey,
1534
+ OrderType.LIMIT,
1535
+ MarketType.PERP,
1536
+ 3, // orderId
1537
+ marketIndex,
1538
+ new BN(12), // price
1539
+ BASE_PRECISION, // quantity
1540
+ PositionDirection.SHORT,
1541
+ vBid,
1542
+ vAsk
1543
+ );
1544
+
1545
+ // auction over
1546
+ const endSlot = 12;
1547
+
1548
+ // should have no crossing orders
1549
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
1550
+ marketIndex,
1551
+ endSlot,
1552
+ MarketType.PERP,
1553
+ {
1554
+ price: vBid.add(vAsk).div(new BN(2)),
1555
+ slot: new BN(endSlot),
1556
+ confidence: new BN(1),
1557
+ hasSufficientNumberOfDataPoints: true,
1558
+ }
1559
+ );
1560
+ expect(nodesToFillBefore.length).to.equal(0);
1561
+
1562
+ // place one market buy order eating 2 of the limit orders
1563
+ insertOrderToDLOB(
1564
+ dlob,
1565
+ user3.publicKey,
1566
+ OrderType.MARKET,
1567
+ MarketType.PERP,
1568
+ 4, // orderId
1569
+ marketIndex,
1570
+ new BN(13), // price
1571
+ new BN(2).mul(BASE_PRECISION), // quantity
1572
+ PositionDirection.LONG,
1573
+ vBid,
1574
+ vAsk
1575
+ );
1576
+
1577
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
1578
+ marketIndex,
1579
+ endSlot,
1580
+ MarketType.PERP,
1581
+ {
1582
+ price: vBid.add(vAsk).div(new BN(2)),
1583
+ slot: new BN(endSlot),
1584
+ confidence: new BN(1),
1585
+ hasSufficientNumberOfDataPoints: true,
1586
+ }
1587
+ );
1588
+ console.log(`Filled nodes: ${nodesToFillAfter.length}`);
1589
+ for (const n of nodesToFillAfter) {
1590
+ printCrossedNodes(n, endSlot);
1591
+ }
1592
+ expect(nodesToFillAfter.length).to.equal(2);
1593
+
1594
+ // taker should fill completely with best maker
1595
+ expect(
1596
+ nodesToFillAfter[0].node.order?.orderId,
1597
+ 'wrong taker orderId'
1598
+ ).to.equal(4);
1599
+ expect(
1600
+ nodesToFillAfter[0].makerNode?.order?.orderId,
1601
+ 'wrong maker orderId'
1602
+ ).to.equal(3);
1603
+
1604
+ // taker should fill completely with second best maker
1605
+ expect(
1606
+ nodesToFillAfter[1].node.order?.orderId,
1607
+ 'wrong taker orderId'
1608
+ ).to.equal(4);
1609
+ expect(
1610
+ nodesToFillAfter[1].makerNode?.order?.orderId,
1611
+ 'wrong maker orderId'
1612
+ ).to.equal(2);
1613
+ });
1614
+
1615
+ it('Test two market orders to fill one limit order', () => {
1616
+ const vAsk = new BN(15);
1617
+ const vBid = new BN(8);
1618
+
1619
+ const mockUserMap = new MockUserMap();
1620
+ const user0 = Keypair.generate();
1621
+ const user0Auth = Keypair.generate();
1622
+ const user1 = Keypair.generate();
1623
+ const user1Auth = Keypair.generate();
1624
+ const user2 = Keypair.generate();
1625
+ const user2Auth = Keypair.generate();
1626
+ const user3 = Keypair.generate();
1627
+ const user3Auth = Keypair.generate();
1628
+ const user4 = Keypair.generate();
1629
+ const user4Auth = Keypair.generate();
1630
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
1631
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user1Auth.publicKey);
1632
+ mockUserMap.addUserAccountAuthority(user2.publicKey, user2Auth.publicKey);
1633
+ mockUserMap.addUserAccountAuthority(user3.publicKey, user3Auth.publicKey);
1634
+ mockUserMap.addUserAccountAuthority(user4.publicKey, user4Auth.publicKey);
1635
+
1636
+ const dlob = new DLOB(
1637
+ mockPerpMarkets,
1638
+ mockSpotMarkets,
1639
+ mockStateAccount,
1640
+ mockUserMap,
1641
+ false
1642
+ );
1643
+ const marketIndex = 0;
1644
+
1645
+ const slot = 12;
1646
+ const oracle = {
1647
+ price: vBid.add(vAsk).div(new BN(2)),
1648
+ slot: new BN(slot),
1649
+ confidence: new BN(1),
1650
+ hasSufficientNumberOfDataPoints: true,
1651
+ };
1652
+
1653
+ // insert some limit sells below vAMM ask, above bid
1654
+ insertOrderToDLOB(
1655
+ dlob,
1656
+ user0.publicKey,
1657
+ OrderType.LIMIT,
1658
+ MarketType.PERP,
1659
+ 1, // orderId
1660
+ marketIndex,
1661
+ new BN(14), // price
1662
+ BASE_PRECISION, // quantity
1663
+ PositionDirection.SHORT,
1664
+ vBid,
1665
+ vAsk
1666
+ );
1667
+ insertOrderToDLOB(
1668
+ dlob,
1669
+ user1.publicKey,
1670
+ OrderType.LIMIT,
1671
+ MarketType.PERP,
1672
+ 2, // orderId
1673
+ marketIndex,
1674
+ new BN(13), // price
1675
+ BASE_PRECISION, // quantity
1676
+ PositionDirection.SHORT,
1677
+ vBid,
1678
+ vAsk
1679
+ );
1680
+ insertOrderToDLOB(
1681
+ dlob,
1682
+ user2.publicKey,
1683
+ OrderType.LIMIT,
1684
+ MarketType.PERP,
1685
+ 3, // orderId
1686
+ marketIndex,
1687
+ new BN(9), // price <-- best price
1688
+ new BN(3).mul(BASE_PRECISION), // quantity
1689
+ PositionDirection.SHORT,
1690
+ vBid,
1691
+ vAsk
1692
+ );
1693
+
1694
+ // should have no crossing orders
1695
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
1696
+ marketIndex,
1697
+ 12, // auction over
1698
+ MarketType.PERP,
1699
+ oracle
1700
+ );
1701
+ expect(nodesToFillBefore.length).to.equal(0);
1702
+
1703
+ // place two market buy orders to eat the best ask
1704
+ insertOrderToDLOB(
1705
+ dlob,
1706
+ user3.publicKey,
1707
+ OrderType.MARKET,
1708
+ MarketType.PERP,
1709
+ 4, // orderId
1710
+ marketIndex,
1711
+ new BN(0), // price
1712
+ new BN(1).mul(BASE_PRECISION), // quantity
1713
+ PositionDirection.LONG,
1714
+ vBid,
1715
+ vAsk
1716
+ );
1717
+ insertOrderToDLOB(
1718
+ dlob,
1719
+ user4.publicKey,
1720
+ OrderType.MARKET,
1721
+ MarketType.PERP,
1722
+ 5, // orderId
1723
+ marketIndex,
1724
+ new BN(0), // price
1725
+ new BN(2).mul(BASE_PRECISION), // quantity
1726
+ PositionDirection.LONG,
1727
+ vBid,
1728
+ vAsk
1729
+ );
1730
+
1731
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
1732
+ marketIndex,
1733
+ slot, // auction over
1734
+ MarketType.PERP,
1735
+ oracle
1736
+ );
1737
+ const mktNodes = dlob.findExpiredNodesToFill(
1738
+ marketIndex,
1739
+ slot,
1740
+ MarketType.PERP
1741
+ );
1742
+ console.log(`market nodes: ${mktNodes.length}`);
1743
+
1744
+ printBookState(dlob, marketIndex, vBid, vAsk, slot, oracle);
1745
+
1746
+ console.log(`Filled nodes: ${nodesToFillAfter.length}`);
1747
+ for (const n of nodesToFillAfter) {
1748
+ printCrossedNodes(n, slot);
1749
+ }
1750
+ expect(nodesToFillAfter.length).to.equal(2);
1751
+
1752
+ // taker should fill completely with best maker
1753
+ expect(nodesToFillAfter[0].node.order?.orderId).to.equal(4);
1754
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId).to.equal(3);
1755
+
1756
+ // taker should fill completely with second best maker
1757
+ expect(nodesToFillAfter[1].node.order?.orderId).to.equal(5);
1758
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId).to.equal(3);
1759
+ });
1760
+
1761
+ it('Test trigger orders', () => {
1762
+ const vAsk = new BN(15);
1763
+ const vBid = new BN(8);
1764
+
1765
+ const mockUserMap = new MockUserMap();
1766
+ const user0 = Keypair.generate();
1767
+ const user0Auth = Keypair.generate();
1768
+ const user1 = Keypair.generate();
1769
+ const user1Auth = Keypair.generate();
1770
+ const user2 = Keypair.generate();
1771
+ const user2Auth = Keypair.generate();
1772
+ const user3 = Keypair.generate();
1773
+ const user3Auth = Keypair.generate();
1774
+ const user4 = Keypair.generate();
1775
+ const user4Auth = Keypair.generate();
1776
+ const user5 = Keypair.generate();
1777
+ const user5Auth = Keypair.generate();
1778
+ const user6 = Keypair.generate();
1779
+ const user6Auth = Keypair.generate();
1780
+ const user7 = Keypair.generate();
1781
+ const user7Auth = Keypair.generate();
1782
+ const user8 = Keypair.generate();
1783
+ const user8Auth = Keypair.generate();
1784
+ const user9 = Keypair.generate();
1785
+ const user9Auth = Keypair.generate();
1786
+ const user10 = Keypair.generate();
1787
+ const user10Auth = Keypair.generate();
1788
+ const user11 = Keypair.generate();
1789
+ const user11Auth = Keypair.generate();
1790
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
1791
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user1Auth.publicKey);
1792
+ mockUserMap.addUserAccountAuthority(user2.publicKey, user2Auth.publicKey);
1793
+ mockUserMap.addUserAccountAuthority(user3.publicKey, user3Auth.publicKey);
1794
+ mockUserMap.addUserAccountAuthority(user4.publicKey, user4Auth.publicKey);
1795
+ mockUserMap.addUserAccountAuthority(user5.publicKey, user5Auth.publicKey);
1796
+ mockUserMap.addUserAccountAuthority(user6.publicKey, user6Auth.publicKey);
1797
+ mockUserMap.addUserAccountAuthority(user7.publicKey, user7Auth.publicKey);
1798
+ mockUserMap.addUserAccountAuthority(user8.publicKey, user8Auth.publicKey);
1799
+ mockUserMap.addUserAccountAuthority(user9.publicKey, user9Auth.publicKey);
1800
+ mockUserMap.addUserAccountAuthority(user10.publicKey, user10Auth.publicKey);
1801
+ mockUserMap.addUserAccountAuthority(user11.publicKey, user11Auth.publicKey);
1802
+
1803
+ const dlob = new DLOB(
1804
+ mockPerpMarkets,
1805
+ mockSpotMarkets,
1806
+ mockStateAccount,
1807
+ mockUserMap,
1808
+ false
1809
+ );
1810
+ const marketIndex = 0;
1811
+
1812
+ const slot = 20;
1813
+ const oracle = {
1814
+ price: vBid.add(vAsk).div(new BN(2)),
1815
+ slot: new BN(slot),
1816
+ confidence: new BN(1),
1817
+ hasSufficientNumberOfDataPoints: true,
1818
+ };
1819
+
1820
+ const orderIdsToTrigger = [1, 2, 3, 4, 5, 6, 7, 8];
1821
+ // const orderIdsToNotTrigger = [9, 10, 11, 12];
1822
+
1823
+ // should trigger limit buy with above condition
1824
+ insertTriggerOrderToDLOB(
1825
+ dlob,
1826
+ user0.publicKey,
1827
+ OrderType.TRIGGER_LIMIT,
1828
+ MarketType.PERP,
1829
+ 1, //orderId
1830
+ marketIndex, // marketIndex
1831
+ vBid, // price
1832
+ BASE_PRECISION, // baseAssetAmount: BN,
1833
+ PositionDirection.LONG,
1834
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
1835
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
1836
+ vBid,
1837
+ vAsk
1838
+ );
1839
+ // should trigger limit sell with above condition
1840
+ insertTriggerOrderToDLOB(
1841
+ dlob,
1842
+ user1.publicKey,
1843
+ OrderType.TRIGGER_LIMIT,
1844
+ MarketType.PERP,
1845
+ 2, //orderId
1846
+ marketIndex, // marketIndex
1847
+ vBid, // price
1848
+ BASE_PRECISION, // baseAssetAmount: BN,
1849
+ PositionDirection.SHORT,
1850
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
1851
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
1852
+ vBid,
1853
+ vAsk
1854
+ );
1855
+ // should trigger market buy with above condition
1856
+ insertTriggerOrderToDLOB(
1857
+ dlob,
1858
+ user2.publicKey,
1859
+ OrderType.TRIGGER_MARKET,
1860
+ MarketType.PERP,
1861
+ 3, //orderId
1862
+ marketIndex, // marketIndex
1863
+ vAsk, // price
1864
+ BASE_PRECISION, // baseAssetAmount: BN,
1865
+ PositionDirection.LONG,
1866
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
1867
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
1868
+ vBid,
1869
+ vAsk
1870
+ );
1871
+ // should trigger market sell with above condition
1872
+ insertTriggerOrderToDLOB(
1873
+ dlob,
1874
+ user3.publicKey,
1875
+ OrderType.TRIGGER_MARKET,
1876
+ MarketType.PERP,
1877
+ 4, //orderId
1878
+ marketIndex, // marketIndex
1879
+ vBid, // price
1880
+ BASE_PRECISION, // baseAssetAmount: BN,
1881
+ PositionDirection.SHORT,
1882
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
1883
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
1884
+ vBid,
1885
+ vAsk
1886
+ );
1887
+ // should trigger limit buy with below condition
1888
+ insertTriggerOrderToDLOB(
1889
+ dlob,
1890
+ user4.publicKey,
1891
+ OrderType.TRIGGER_LIMIT,
1892
+ MarketType.PERP,
1893
+ 5, //orderId
1894
+ marketIndex, // marketIndex
1895
+ vBid, // price
1896
+ BASE_PRECISION, // baseAssetAmount: BN,
1897
+ PositionDirection.LONG,
1898
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
1899
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
1900
+ vBid,
1901
+ vAsk
1902
+ );
1903
+ // should trigger limit sell with below condition
1904
+ insertTriggerOrderToDLOB(
1905
+ dlob,
1906
+ user5.publicKey,
1907
+ OrderType.TRIGGER_LIMIT,
1908
+ MarketType.PERP,
1909
+ 6, //orderId
1910
+ marketIndex, // marketIndex
1911
+ vBid, // price
1912
+ BASE_PRECISION, // baseAssetAmount: BN,
1913
+ PositionDirection.SHORT,
1914
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
1915
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
1916
+ vBid,
1917
+ vAsk
1918
+ );
1919
+ // should trigger market buy with below condition
1920
+ insertTriggerOrderToDLOB(
1921
+ dlob,
1922
+ user6.publicKey,
1923
+ OrderType.TRIGGER_MARKET,
1924
+ MarketType.PERP,
1925
+ 7, //orderId
1926
+ marketIndex, // marketIndex
1927
+ vBid, // price
1928
+ BASE_PRECISION, // baseAssetAmount: BN,
1929
+ PositionDirection.LONG,
1930
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
1931
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
1932
+ vBid,
1933
+ vAsk
1934
+ );
1935
+ // should trigger market sell with below condition
1936
+ insertTriggerOrderToDLOB(
1937
+ dlob,
1938
+ user7.publicKey,
1939
+ OrderType.TRIGGER_MARKET,
1940
+ MarketType.PERP,
1941
+ 8, //orderId
1942
+ marketIndex, // marketIndex
1943
+ vBid, // price
1944
+ BASE_PRECISION, // baseAssetAmount: BN,
1945
+ PositionDirection.LONG,
1946
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
1947
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
1948
+ vBid,
1949
+ vAsk
1950
+ );
1951
+
1952
+ // should NOT trigger market sell with above condition
1953
+ insertTriggerOrderToDLOB(
1954
+ dlob,
1955
+ user8.publicKey,
1956
+ OrderType.TRIGGER_MARKET,
1957
+ MarketType.PERP,
1958
+ 9, //orderId
1959
+ marketIndex, // marketIndex
1960
+ vBid, // price
1961
+ BASE_PRECISION, // baseAssetAmount: BN,
1962
+ PositionDirection.SHORT,
1963
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
1964
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
1965
+ vBid,
1966
+ vAsk
1967
+ );
1968
+ // should NOT trigger market sell with below condition
1969
+ insertTriggerOrderToDLOB(
1970
+ dlob,
1971
+ user9.publicKey,
1972
+ OrderType.TRIGGER_MARKET,
1973
+ MarketType.PERP,
1974
+ 10, //orderId
1975
+ marketIndex, // marketIndex
1976
+ vBid, // price
1977
+ BASE_PRECISION, // baseAssetAmount: BN,
1978
+ PositionDirection.SHORT,
1979
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
1980
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
1981
+ vBid,
1982
+ vAsk
1983
+ );
1984
+ // should NOT trigger market buy with above condition
1985
+ insertTriggerOrderToDLOB(
1986
+ dlob,
1987
+ user10.publicKey,
1988
+ OrderType.TRIGGER_MARKET,
1989
+ MarketType.PERP,
1990
+ 11, //orderId
1991
+ marketIndex, // marketIndex
1992
+ vBid, // price
1993
+ BASE_PRECISION, // baseAssetAmount: BN,
1994
+ PositionDirection.LONG,
1995
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
1996
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
1997
+ vBid,
1998
+ vAsk
1999
+ );
2000
+ // should NOT trigger market buy with below condition
2001
+ insertTriggerOrderToDLOB(
2002
+ dlob,
2003
+ user11.publicKey,
2004
+ OrderType.TRIGGER_MARKET,
2005
+ MarketType.PERP,
2006
+ 12, //orderId
2007
+ marketIndex, // marketIndex
2008
+ vBid, // price
2009
+ BASE_PRECISION, // baseAssetAmount: BN,
2010
+ PositionDirection.LONG,
2011
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
2012
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
2013
+ vBid,
2014
+ vAsk
2015
+ );
2016
+
2017
+ const nodesToTrigger = dlob.findNodesToTrigger(
2018
+ marketIndex,
2019
+ slot,
2020
+ oracle.price,
2021
+ MarketType.PERP
2022
+ );
2023
+ console.log(`nodesToTriggeR: ${nodesToTrigger.length}`);
2024
+ for (const [idx, n] of nodesToTrigger.entries()) {
2025
+ expect(n.node.order?.orderId).to.equal(orderIdsToTrigger[idx]);
2026
+ console.log(`nodeToTrigger: ${n.node.order?.orderId.toString()}`);
2027
+ }
2028
+ });
2029
+
2030
+ it('Test will return expired market orders to fill', () => {
2031
+ const vAsk = new BN(15);
2032
+ const vBid = new BN(8);
2033
+
2034
+ const mockUserMap = new MockUserMap();
2035
+ const user0 = Keypair.generate();
2036
+ const user0Auth = Keypair.generate();
2037
+ const user1 = Keypair.generate();
2038
+ const user1Auth = Keypair.generate();
2039
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
2040
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user1Auth.publicKey);
2041
+
2042
+ const dlob = new DLOB(
2043
+ mockPerpMarkets,
2044
+ mockSpotMarkets,
2045
+ mockStateAccount,
2046
+ mockUserMap,
2047
+ false
2048
+ );
2049
+ const marketIndex = 0;
2050
+
2051
+ const slot = 20;
2052
+ const ts = 20;
2053
+ const maxTs = new BN(30);
2054
+
2055
+ // non crossing bid
2056
+ insertOrderToDLOB(
2057
+ dlob,
2058
+ user0.publicKey,
2059
+ OrderType.MARKET,
2060
+ MarketType.PERP,
2061
+ 255, // orderId
2062
+ marketIndex,
2063
+ new BN(2), // price, very low, don't cross vamm
2064
+ BASE_PRECISION, // quantity
2065
+ PositionDirection.LONG,
2066
+ vBid,
2067
+ vAsk,
2068
+ new BN(slot),
2069
+ maxTs
2070
+ );
2071
+ insertOrderToDLOB(
2072
+ dlob,
2073
+ user1.publicKey,
2074
+ OrderType.MARKET,
2075
+ MarketType.PERP,
2076
+ 2, // orderId
2077
+ marketIndex,
2078
+ new BN(30), // price, very high, don't cross vamm
2079
+ BASE_PRECISION, // quantity
2080
+ PositionDirection.SHORT,
2081
+ vAsk,
2082
+ vBid,
2083
+ new BN(slot),
2084
+ maxTs
2085
+ );
2086
+
2087
+ // order auction is not yet complete, and order is not expired.
2088
+ const slot0 = slot;
2089
+ const ts0 = ts;
2090
+ const nodesToFillBefore = dlob.findNodesToFill(
2091
+ marketIndex,
2092
+ vBid,
2093
+ vAsk,
2094
+ slot0,
2095
+ ts0,
2096
+ MarketType.PERP,
2097
+ {
2098
+ price: vBid.add(vAsk).div(new BN(2)),
2099
+ slot: new BN(slot0),
2100
+ confidence: new BN(1),
2101
+ hasSufficientNumberOfDataPoints: true,
2102
+ }
2103
+ );
2104
+ expect(nodesToFillBefore.length).to.equal(0);
2105
+
2106
+ // should get order to fill after timeInForce
2107
+ const slot1 = slot0 + 20;
2108
+ const ts1 = ts0 + 20; // overshoots expiry
2109
+ const nodesToFillAfter = dlob.findNodesToFill(
2110
+ marketIndex,
2111
+ vBid,
2112
+ vAsk,
2113
+ slot1, // auction is over, and order ix expired
2114
+ ts1,
2115
+ MarketType.PERP,
2116
+ {
2117
+ price: vBid.add(vAsk).div(new BN(2)),
2118
+ slot: new BN(slot1),
2119
+ confidence: new BN(1),
2120
+ hasSufficientNumberOfDataPoints: true,
2121
+ }
2122
+ );
2123
+ console.log(`Filled nodes: ${nodesToFillAfter.length}`);
2124
+ for (const n of nodesToFillAfter) {
2125
+ printCrossedNodes(n, slot1);
2126
+ }
2127
+ expect(nodesToFillAfter.length).to.equal(2);
2128
+
2129
+ // check that the nodes have no makers
2130
+ expect(nodesToFillAfter[0].makerNode).to.equal(undefined);
2131
+ expect(nodesToFillAfter[1].makerNode).to.equal(undefined);
2132
+ });
2133
+
2134
+ it('Test skips vAMM and fills market buy order with floating limit order during auction', () => {
2135
+ const vAsk = new BN(15).mul(PRICE_PRECISION);
2136
+ const vBid = new BN(8).mul(PRICE_PRECISION);
2137
+
2138
+ const mockUserMap = new MockUserMap();
2139
+ const user0 = Keypair.generate();
2140
+ const user0Auth = Keypair.generate();
2141
+ const user1 = Keypair.generate();
2142
+ const user1Auth = Keypair.generate();
2143
+ const user2 = Keypair.generate();
2144
+ const user2Auth = Keypair.generate();
2145
+ const user3 = Keypair.generate();
2146
+ const user3Auth = Keypair.generate();
2147
+ const user4 = Keypair.generate();
2148
+ const user4Auth = Keypair.generate();
2149
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
2150
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user1Auth.publicKey);
2151
+ mockUserMap.addUserAccountAuthority(user2.publicKey, user2Auth.publicKey);
2152
+ mockUserMap.addUserAccountAuthority(user3.publicKey, user3Auth.publicKey);
2153
+ mockUserMap.addUserAccountAuthority(user4.publicKey, user4Auth.publicKey);
2154
+
2155
+ const dlob = new DLOB(
2156
+ mockPerpMarkets,
2157
+ mockSpotMarkets,
2158
+ mockStateAccount,
2159
+ mockUserMap,
2160
+ false
2161
+ );
2162
+ const marketIndex = 0;
2163
+
2164
+ const slot = 12;
2165
+ const ts = 12;
2166
+ const oracle = {
2167
+ price: vBid.add(vAsk).div(new BN(2)),
2168
+ slot: new BN(slot),
2169
+ confidence: new BN(1),
2170
+ hasSufficientNumberOfDataPoints: true,
2171
+ };
2172
+
2173
+ // insert some floating limit sells above vAMM ask
2174
+ insertOrderToDLOB(
2175
+ dlob,
2176
+ user0.publicKey,
2177
+ OrderType.LIMIT,
2178
+ MarketType.PERP,
2179
+ 1, // orderId
2180
+ marketIndex,
2181
+ new BN(14).mul(PRICE_PRECISION), // price
2182
+ BASE_PRECISION, // quantity
2183
+ PositionDirection.SHORT,
2184
+ vBid,
2185
+ vAsk,
2186
+ new BN(slot),
2187
+ ZERO,
2188
+ new BN(1).mul(PRICE_PRECISION)
2189
+ );
2190
+ insertOrderToDLOB(
2191
+ dlob,
2192
+ user1.publicKey,
2193
+ OrderType.LIMIT,
2194
+ MarketType.PERP,
2195
+ 2, // orderId
2196
+ marketIndex,
2197
+ new BN(13).mul(PRICE_PRECISION), // price
2198
+ BASE_PRECISION, // quantity
2199
+ PositionDirection.SHORT,
2200
+ vBid,
2201
+ vAsk,
2202
+ new BN(slot),
2203
+ ZERO,
2204
+ new BN(1).mul(PRICE_PRECISION)
2205
+ );
2206
+ insertOrderToDLOB(
2207
+ dlob,
2208
+ user2.publicKey,
2209
+ OrderType.LIMIT,
2210
+ MarketType.PERP,
2211
+ 3, // orderId
2212
+ marketIndex,
2213
+ new BN(9).mul(PRICE_PRECISION), // price <-- best price
2214
+ new BN(3).mul(BASE_PRECISION), // quantity
2215
+ PositionDirection.SHORT,
2216
+ vBid,
2217
+ vAsk,
2218
+ new BN(slot),
2219
+ ZERO,
2220
+ new BN(1).mul(PRICE_PRECISION)
2221
+ );
2222
+
2223
+ // should have no crossing orders
2224
+ const nodesToFillBefore = dlob.findNodesToFill(
2225
+ marketIndex,
2226
+ vBid,
2227
+ vAsk,
2228
+ slot,
2229
+ ts,
2230
+ MarketType.PERP,
2231
+ {
2232
+ price: vBid.add(vAsk).div(new BN(2)),
2233
+ slot: new BN(slot),
2234
+ confidence: new BN(1),
2235
+ hasSufficientNumberOfDataPoints: true,
2236
+ }
2237
+ );
2238
+ expect(nodesToFillBefore.length).to.equal(0);
2239
+
2240
+ // place two market buy orders to eat the best ask
2241
+ insertOrderToDLOB(
2242
+ dlob,
2243
+ user3.publicKey,
2244
+ OrderType.MARKET,
2245
+ MarketType.PERP,
2246
+ 4, // orderId
2247
+ marketIndex,
2248
+ new BN(15).mul(PRICE_PRECISION), // price
2249
+ new BN(1).mul(BASE_PRECISION), // quantity
2250
+ PositionDirection.LONG,
2251
+ vBid,
2252
+ vAsk
2253
+ );
2254
+ insertOrderToDLOB(
2255
+ dlob,
2256
+ user4.publicKey,
2257
+ OrderType.MARKET,
2258
+ MarketType.PERP,
2259
+ 5, // orderId
2260
+ marketIndex,
2261
+ new BN(15).mul(PRICE_PRECISION), // price
2262
+ new BN(2).mul(BASE_PRECISION), // quantity
2263
+ PositionDirection.LONG,
2264
+ vBid,
2265
+ vAsk
2266
+ );
2267
+
2268
+ const nodesToFillAfter = dlob.findNodesToFill(
2269
+ marketIndex,
2270
+ vBid,
2271
+ vAsk,
2272
+ slot,
2273
+ ts,
2274
+ MarketType.PERP,
2275
+ {
2276
+ price: vBid.add(vAsk).div(new BN(2)),
2277
+ slot: new BN(slot),
2278
+ confidence: new BN(1),
2279
+ hasSufficientNumberOfDataPoints: true,
2280
+ }
2281
+ );
2282
+ const mktNodes = dlob.findExpiredNodesToFill(
2283
+ marketIndex,
2284
+ slot,
2285
+ MarketType.PERP
2286
+ );
2287
+ console.log(`market nodes: ${mktNodes.length}`);
2288
+
2289
+ printBookState(dlob, marketIndex, vBid, vAsk, slot, oracle);
2290
+
2291
+ console.log(`Filled nodes: ${nodesToFillAfter.length}`);
2292
+ for (const n of nodesToFillAfter) {
2293
+ printCrossedNodes(n, slot);
2294
+ }
2295
+ expect(nodesToFillAfter.length).to.equal(3);
2296
+
2297
+ // taker should fill first order completely with best maker (1/1)
2298
+ expect(nodesToFillAfter[0].node.order?.orderId).to.equal(4);
2299
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId).to.equal(1);
2300
+
2301
+ // taker should fill partially with second best maker (1/2)
2302
+ expect(nodesToFillAfter[1].node.order?.orderId).to.equal(5);
2303
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId).to.equal(2);
2304
+
2305
+ // taker should fill completely with third best maker (2/2)
2306
+ expect(nodesToFillAfter[2].node.order?.orderId).to.equal(5);
2307
+ expect(nodesToFillAfter[2].makerNode?.order?.orderId).to.equal(3);
2308
+ });
2309
+
2310
+ it('Test fills market buy order with better priced vAMM after auction', () => {
2311
+ const vAsk = new BN(15).mul(PRICE_PRECISION);
2312
+ const vBid = new BN(8).mul(PRICE_PRECISION);
2313
+
2314
+ const mockUserMap = new MockUserMap();
2315
+ const user0 = Keypair.generate();
2316
+ const user0Auth = Keypair.generate();
2317
+ const user1 = Keypair.generate();
2318
+ const user1Auth = Keypair.generate();
2319
+ const user2 = Keypair.generate();
2320
+ const user2Auth = Keypair.generate();
2321
+ const user3 = Keypair.generate();
2322
+ const user3Auth = Keypair.generate();
2323
+ const user4 = Keypair.generate();
2324
+ const user4Auth = Keypair.generate();
2325
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
2326
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user1Auth.publicKey);
2327
+ mockUserMap.addUserAccountAuthority(user2.publicKey, user2Auth.publicKey);
2328
+ mockUserMap.addUserAccountAuthority(user3.publicKey, user3Auth.publicKey);
2329
+ mockUserMap.addUserAccountAuthority(user4.publicKey, user4Auth.publicKey);
2330
+
2331
+ const dlob = new DLOB(
2332
+ mockPerpMarkets,
2333
+ mockSpotMarkets,
2334
+ mockStateAccount,
2335
+ mockUserMap,
2336
+ false
2337
+ );
2338
+ const marketIndex = 0;
2339
+
2340
+ const slot = 12;
2341
+ const ts = 12;
2342
+ const oracle = {
2343
+ price: vBid.add(vAsk).div(new BN(2)),
2344
+ slot: new BN(slot),
2345
+ confidence: new BN(1),
2346
+ hasSufficientNumberOfDataPoints: true,
2347
+ };
2348
+
2349
+ // insert some floating limit sells above vAMM ask
2350
+ insertOrderToDLOB(
2351
+ dlob,
2352
+ user0.publicKey,
2353
+ OrderType.LIMIT,
2354
+ MarketType.PERP,
2355
+ 1, // orderId
2356
+ marketIndex,
2357
+ new BN(14).mul(PRICE_PRECISION), // price
2358
+ BASE_PRECISION, // quantity
2359
+ PositionDirection.SHORT,
2360
+ vBid,
2361
+ vAsk,
2362
+ new BN(slot),
2363
+ ZERO,
2364
+ new BN(3).mul(PRICE_PRECISION)
2365
+ );
2366
+ insertOrderToDLOB(
2367
+ dlob,
2368
+ user1.publicKey,
2369
+ OrderType.LIMIT,
2370
+ MarketType.PERP,
2371
+ 2, // orderId
2372
+ marketIndex,
2373
+ new BN(13).mul(PRICE_PRECISION), // price
2374
+ BASE_PRECISION, // quantity
2375
+ PositionDirection.SHORT,
2376
+ vBid,
2377
+ vAsk,
2378
+ new BN(slot),
2379
+ ZERO,
2380
+ new BN(4).mul(PRICE_PRECISION)
2381
+ );
2382
+ insertOrderToDLOB(
2383
+ dlob,
2384
+ user2.publicKey,
2385
+ OrderType.LIMIT,
2386
+ MarketType.PERP,
2387
+ 3, // orderId
2388
+ marketIndex,
2389
+ new BN(9).mul(PRICE_PRECISION), // price <-- best price
2390
+ new BN(3).mul(BASE_PRECISION), // quantity
2391
+ PositionDirection.SHORT,
2392
+ vBid,
2393
+ vAsk,
2394
+ new BN(slot),
2395
+ ZERO,
2396
+ new BN(5).mul(PRICE_PRECISION)
2397
+ );
2398
+
2399
+ // should have no crossing orders
2400
+ const auctionOverSlot = slot * 10;
2401
+ const auctionOverTs = ts * 10;
2402
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
2403
+ marketIndex,
2404
+ auctionOverSlot, // auction over
2405
+ MarketType.PERP,
2406
+ oracle
2407
+ );
2408
+ expect(nodesToFillBefore.length).to.equal(0);
2409
+
2410
+ // place two market buy orders
2411
+ insertOrderToDLOB(
2412
+ dlob,
2413
+ user3.publicKey,
2414
+ OrderType.MARKET,
2415
+ MarketType.PERP,
2416
+ 4, // orderId
2417
+ marketIndex,
2418
+ new BN(15).mul(PRICE_PRECISION), // price
2419
+ new BN(1).mul(BASE_PRECISION), // quantity
2420
+ PositionDirection.LONG,
2421
+ vBid,
2422
+ vAsk,
2423
+ new BN(slot),
2424
+ ZERO
2425
+ );
2426
+ insertOrderToDLOB(
2427
+ dlob,
2428
+ user4.publicKey,
2429
+ OrderType.MARKET,
2430
+ MarketType.PERP,
2431
+ 5, // orderId
2432
+ marketIndex,
2433
+ new BN(15).mul(PRICE_PRECISION), // price
2434
+ new BN(2).mul(BASE_PRECISION), // quantity
2435
+ PositionDirection.LONG,
2436
+ vBid,
2437
+ vAsk,
2438
+ new BN(slot),
2439
+ ZERO
2440
+ );
2441
+
2442
+ const nodesToFillAfter = dlob.findNodesToFill(
2443
+ marketIndex,
2444
+ vBid,
2445
+ vAsk,
2446
+ auctionOverTs, // auction in progress
2447
+ ts,
2448
+ MarketType.PERP,
2449
+ oracle
2450
+ );
2451
+
2452
+ console.log(`Filled nodes: ${nodesToFillAfter.length}`);
2453
+ for (const n of nodesToFillAfter) {
2454
+ printCrossedNodes(n, auctionOverSlot);
2455
+ }
2456
+ expect(nodesToFillAfter.length).to.equal(2);
2457
+
2458
+ // taker should fill completely with best maker
2459
+ expect(nodesToFillAfter[0].node.order?.orderId).to.equal(4);
2460
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId).to.equal(1);
2461
+
2462
+ // taker should fill the rest with the vAMM
2463
+ expect(nodesToFillAfter[1].node.order?.orderId).to.equal(5);
2464
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId).to.equal(undefined);
2465
+ });
2466
+
2467
+ it('Test skips vAMM and fills market sell order with floating limit buys during auction', () => {
2468
+ const vAsk = new BN(15).mul(PRICE_PRECISION);
2469
+ const vBid = new BN(8).mul(PRICE_PRECISION);
2470
+
2471
+ const mockUserMap = new MockUserMap();
2472
+ const user0 = Keypair.generate();
2473
+ const user0Auth = Keypair.generate();
2474
+ const user1 = Keypair.generate();
2475
+ const user1Auth = Keypair.generate();
2476
+ const user2 = Keypair.generate();
2477
+ const user2Auth = Keypair.generate();
2478
+ const user3 = Keypair.generate();
2479
+ const user3Auth = Keypair.generate();
2480
+ const user4 = Keypair.generate();
2481
+ const user4Auth = Keypair.generate();
2482
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
2483
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user1Auth.publicKey);
2484
+ mockUserMap.addUserAccountAuthority(user2.publicKey, user2Auth.publicKey);
2485
+ mockUserMap.addUserAccountAuthority(user3.publicKey, user3Auth.publicKey);
2486
+ mockUserMap.addUserAccountAuthority(user4.publicKey, user4Auth.publicKey);
2487
+
2488
+ const dlob = new DLOB(
2489
+ mockPerpMarkets,
2490
+ mockSpotMarkets,
2491
+ mockStateAccount,
2492
+ mockUserMap,
2493
+ false
2494
+ );
2495
+ const marketIndex = 0;
2496
+
2497
+ const slot = 12;
2498
+ const oracle = {
2499
+ price: vBid.add(vAsk).div(new BN(2)), // 11.5
2500
+ slot: new BN(slot),
2501
+ confidence: new BN(1),
2502
+ hasSufficientNumberOfDataPoints: true,
2503
+ };
2504
+
2505
+ // insert some floating limit buy below vAMM bid
2506
+ insertOrderToDLOB(
2507
+ dlob,
2508
+ user0.publicKey,
2509
+ OrderType.LIMIT,
2510
+ MarketType.PERP,
2511
+ 2, // orderId
2512
+ marketIndex,
2513
+ new BN(0).mul(PRICE_PRECISION), // price, ignored since it's a floating limit
2514
+ BASE_PRECISION, // quantity
2515
+ PositionDirection.LONG,
2516
+ vBid,
2517
+ vAsk,
2518
+ new BN(slot),
2519
+ ZERO,
2520
+ new BN(-4).mul(PRICE_PRECISION) // second best bid, but worse than vBid (8): 7.5
2521
+ );
2522
+ insertOrderToDLOB(
2523
+ dlob,
2524
+ user1.publicKey,
2525
+ OrderType.LIMIT,
2526
+ MarketType.PERP,
2527
+ 3, // orderId
2528
+ marketIndex,
2529
+ new BN(0).mul(PRICE_PRECISION), // price; ignored since it's a floating limit
2530
+ new BN(1).mul(BASE_PRECISION), // quantity
2531
+ PositionDirection.LONG,
2532
+ vBid,
2533
+ vAsk,
2534
+ new BN(slot),
2535
+ ZERO,
2536
+ new BN(-1).mul(PRICE_PRECISION) // best price: 10.5
2537
+ );
2538
+ insertOrderToDLOB(
2539
+ dlob,
2540
+ user2.publicKey,
2541
+ OrderType.LIMIT,
2542
+ MarketType.PERP,
2543
+ 1, // orderId
2544
+ marketIndex,
2545
+ new BN(0).mul(PRICE_PRECISION), // price; ignored since it's a floating limit
2546
+ BASE_PRECISION, // quantity
2547
+ PositionDirection.LONG,
2548
+ vBid,
2549
+ vAsk,
2550
+ new BN(slot),
2551
+ ZERO,
2552
+ new BN(-5).mul(PRICE_PRECISION) // third best bid, worse than vBid (8): 6.5
2553
+ );
2554
+
2555
+ // should have no crossing orders
2556
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
2557
+ marketIndex,
2558
+ slot,
2559
+ MarketType.PERP,
2560
+ oracle
2561
+ );
2562
+ expect(nodesToFillBefore.length).to.equal(0);
2563
+
2564
+ // place two market sell orders to eat the best bid
2565
+ insertOrderToDLOB(
2566
+ dlob,
2567
+ user3.publicKey,
2568
+ OrderType.MARKET,
2569
+ MarketType.PERP,
2570
+ 4, // orderId
2571
+ marketIndex,
2572
+ new BN(5).mul(PRICE_PRECISION), // price
2573
+ new BN(1).mul(BASE_PRECISION), // quantity
2574
+ PositionDirection.SHORT,
2575
+ vBid,
2576
+ vAsk
2577
+ );
2578
+ insertOrderToDLOB(
2579
+ dlob,
2580
+ user4.publicKey,
2581
+ OrderType.MARKET,
2582
+ MarketType.PERP,
2583
+ 5, // orderId
2584
+ marketIndex,
2585
+ new BN(4).mul(PRICE_PRECISION), // price
2586
+ new BN(2).mul(BASE_PRECISION), // quantity
2587
+ PositionDirection.SHORT,
2588
+ vBid,
2589
+ vAsk
2590
+ );
2591
+
2592
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
2593
+ marketIndex,
2594
+ slot, // auction in progress
2595
+ MarketType.PERP,
2596
+ oracle
2597
+ );
2598
+ const mktNodes = dlob.findExpiredNodesToFill(
2599
+ marketIndex,
2600
+ slot,
2601
+ MarketType.PERP
2602
+ );
2603
+ console.log(`market nodes: ${mktNodes.length}`);
2604
+
2605
+ printBookState(dlob, marketIndex, vBid, vAsk, slot, oracle);
2606
+
2607
+ console.log(`Filled nodes: ${nodesToFillAfter.length}`);
2608
+ for (const n of nodesToFillAfter) {
2609
+ printCrossedNodes(n, slot);
2610
+ }
2611
+ expect(nodesToFillAfter.length).to.equal(3);
2612
+
2613
+ // taker should fill first order completely with best maker (1/1)
2614
+ expect(
2615
+ nodesToFillAfter[0].node.order?.orderId,
2616
+ 'wrong taker orderId'
2617
+ ).to.equal(4);
2618
+ expect(
2619
+ nodesToFillAfter[0].makerNode?.order?.orderId,
2620
+ 'wrong maker orderId'
2621
+ ).to.equal(3);
2622
+
2623
+ // taker should fill partially with second best maker (1/2)
2624
+ expect(
2625
+ nodesToFillAfter[1].node.order?.orderId,
2626
+ 'wrong maker orderId'
2627
+ ).to.equal(5);
2628
+ expect(
2629
+ nodesToFillAfter[1].makerNode?.order?.orderId,
2630
+ 'wrong maker orderId'
2631
+ ).to.equal(2);
2632
+
2633
+ // taker should fill completely with third best maker (2/2)
2634
+ expect(
2635
+ nodesToFillAfter[2].node.order?.orderId,
2636
+ 'wrong taker orderId'
2637
+ ).to.equal(5);
2638
+ expect(
2639
+ nodesToFillAfter[2].makerNode?.order?.orderId,
2640
+ 'wrong maker orderId'
2641
+ ).to.equal(1);
2642
+ });
2643
+
2644
+ it('Test fills market sell order with better priced vAMM after auction', () => {
2645
+ const vAsk = new BN(15).mul(PRICE_PRECISION);
2646
+ const vBid = new BN(8).mul(PRICE_PRECISION);
2647
+
2648
+ const mockUserMap = new MockUserMap();
2649
+ const user0 = Keypair.generate();
2650
+ const user0Auth = Keypair.generate();
2651
+ const user1 = Keypair.generate();
2652
+ const user1Auth = Keypair.generate();
2653
+ const user2 = Keypair.generate();
2654
+ const user2Auth = Keypair.generate();
2655
+ const user3 = Keypair.generate();
2656
+ const user3Auth = Keypair.generate();
2657
+ const user4 = Keypair.generate();
2658
+ const user4Auth = Keypair.generate();
2659
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
2660
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user1Auth.publicKey);
2661
+ mockUserMap.addUserAccountAuthority(user2.publicKey, user2Auth.publicKey);
2662
+ mockUserMap.addUserAccountAuthority(user3.publicKey, user3Auth.publicKey);
2663
+ mockUserMap.addUserAccountAuthority(user4.publicKey, user4Auth.publicKey);
2664
+
2665
+ const dlob = new DLOB(
2666
+ mockPerpMarkets,
2667
+ mockSpotMarkets,
2668
+ mockStateAccount,
2669
+ mockUserMap,
2670
+ false
2671
+ );
2672
+ const marketIndex = 0;
2673
+
2674
+ const slot = 12;
2675
+ const ts = 12;
2676
+ const oracle = {
2677
+ price: vBid.add(vAsk).div(new BN(2)), // 11.5
2678
+ slot: new BN(slot),
2679
+ confidence: new BN(1),
2680
+ hasSufficientNumberOfDataPoints: true,
2681
+ };
2682
+
2683
+ // insert some floating limit buy below vAMM bid
2684
+ insertOrderToDLOB(
2685
+ dlob,
2686
+ user0.publicKey,
2687
+ OrderType.LIMIT,
2688
+ MarketType.PERP,
2689
+ 2, // orderId
2690
+ marketIndex,
2691
+ new BN(0).mul(PRICE_PRECISION), // price, ignored since it's a floating limit
2692
+ BASE_PRECISION, // quantity
2693
+ PositionDirection.LONG,
2694
+ vBid,
2695
+ vAsk,
2696
+ new BN(slot),
2697
+ ZERO,
2698
+ new BN(-4).mul(PRICE_PRECISION) // second best bid, but worse than vBid (8): 7.5
2699
+ );
2700
+ insertOrderToDLOB(
2701
+ dlob,
2702
+ user1.publicKey,
2703
+ OrderType.LIMIT,
2704
+ MarketType.PERP,
2705
+ 3, // orderId
2706
+ marketIndex,
2707
+ new BN(0).mul(PRICE_PRECISION), // price; ignored since it's a floating limit
2708
+ new BN(1).mul(BASE_PRECISION), // quantity
2709
+ PositionDirection.LONG,
2710
+ vBid,
2711
+ vAsk,
2712
+ new BN(slot),
2713
+ ZERO,
2714
+ new BN(-1).mul(PRICE_PRECISION) // best price: 10.5
2715
+ );
2716
+ insertOrderToDLOB(
2717
+ dlob,
2718
+ user2.publicKey,
2719
+ OrderType.LIMIT,
2720
+ MarketType.PERP,
2721
+ 1, // orderId
2722
+ marketIndex,
2723
+ new BN(0).mul(PRICE_PRECISION), // price; ignored since it's a floating limit
2724
+ BASE_PRECISION, // quantity
2725
+ PositionDirection.LONG,
2726
+ vBid,
2727
+ vAsk,
2728
+ new BN(slot),
2729
+ ZERO,
2730
+ new BN(-5).mul(PRICE_PRECISION) // third best bid, worse than vBid (8): 6.5
2731
+ );
2732
+
2733
+ console.log('DLOB state before fill:');
2734
+ printBookState(dlob, marketIndex, vBid, vAsk, slot, oracle);
2735
+
2736
+ const nodesToFillBefore = dlob.findNodesToFill(
2737
+ marketIndex,
2738
+ vBid,
2739
+ vAsk,
2740
+ slot,
2741
+ ts,
2742
+ MarketType.PERP,
2743
+ oracle
2744
+ );
2745
+ expect(nodesToFillBefore.length).to.equal(0);
2746
+
2747
+ // place two market sell orders to eat the best bid
2748
+ insertOrderToDLOB(
2749
+ dlob,
2750
+ user3.publicKey,
2751
+ OrderType.MARKET,
2752
+ MarketType.PERP,
2753
+ 4, // orderId
2754
+ marketIndex,
2755
+ new BN(5).mul(PRICE_PRECISION), // price
2756
+ new BN(1).mul(BASE_PRECISION), // quantity, should consume best bid floating limit
2757
+ PositionDirection.SHORT,
2758
+ vBid,
2759
+ vAsk,
2760
+ new BN(slot)
2761
+ );
2762
+ insertOrderToDLOB(
2763
+ dlob,
2764
+ user4.publicKey,
2765
+ OrderType.MARKET,
2766
+ MarketType.PERP,
2767
+ 5, // orderId
2768
+ marketIndex,
2769
+ // new BN(8).mul(PRICE_PRECISION), // price, this price will fill with vamm
2770
+ new BN(4).mul(PRICE_PRECISION), // price, this SHOULD fill with vamm
2771
+ new BN(2).mul(BASE_PRECISION), // quantity, should be filled with next best bid (vAMM)
2772
+ PositionDirection.SHORT,
2773
+ vBid,
2774
+ vAsk,
2775
+ new BN(slot)
2776
+ );
2777
+
2778
+ // auction ends, but order not expired
2779
+ const afterAuctionSlot = slot + 11;
2780
+ const afterAuctionTs = ts + 11;
2781
+ printBookState(dlob, marketIndex, vBid, vAsk, afterAuctionSlot, oracle);
2782
+
2783
+ const nodesToFillAfter = dlob.findNodesToFill(
2784
+ marketIndex,
2785
+ vBid,
2786
+ vAsk,
2787
+ afterAuctionSlot,
2788
+ afterAuctionTs,
2789
+ MarketType.PERP,
2790
+ oracle
2791
+ );
2792
+
2793
+ console.log(`Filled nodes: ${nodesToFillAfter.length}`);
2794
+ for (const n of nodesToFillAfter) {
2795
+ printCrossedNodes(n, afterAuctionSlot);
2796
+ }
2797
+
2798
+ // taker should fill first order completely with best maker (1/1)
2799
+ expect(
2800
+ nodesToFillAfter[0].node.order?.orderId,
2801
+ 'wrong taker orderId'
2802
+ ).to.equal(4);
2803
+ expect(
2804
+ nodesToFillAfter[0].makerNode?.order?.orderId,
2805
+ 'wrong maker orderId'
2806
+ ).to.equal(3);
2807
+
2808
+ // taker should fill second order completely with vamm
2809
+ expect(
2810
+ nodesToFillAfter[1].node.order?.orderId,
2811
+ 'wrong taker orderId'
2812
+ ).to.equal(5);
2813
+ expect(
2814
+ nodesToFillAfter[1].makerNode?.order?.orderId,
2815
+ 'wrong maker orderId'
2816
+ ).to.equal(2); // filler should match the DLOB makers, protocol will fill the taker with vAMM if it offers a better price.
2817
+
2818
+ expect(nodesToFillAfter.length).to.equal(3);
2819
+ });
2820
+
2821
+ it('Test fills crossing bids with vAMM after auction ends', () => {
2822
+ const vAsk = new BN(15).mul(PRICE_PRECISION);
2823
+ const vBid = new BN(8).mul(PRICE_PRECISION);
2824
+
2825
+ const mockUserMap = new MockUserMap();
2826
+ const user0 = Keypair.generate();
2827
+ const user0Auth = Keypair.generate();
2828
+ const user1 = Keypair.generate();
2829
+ const user1Auth = Keypair.generate();
2830
+ const user2 = Keypair.generate();
2831
+ const user2Auth = Keypair.generate();
2832
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
2833
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user1Auth.publicKey);
2834
+ mockUserMap.addUserAccountAuthority(user2.publicKey, user2Auth.publicKey);
2835
+
2836
+ const dlob = new DLOB(
2837
+ mockPerpMarkets,
2838
+ mockSpotMarkets,
2839
+ mockStateAccount,
2840
+ mockUserMap,
2841
+ false
2842
+ );
2843
+ const marketIndex = 0;
2844
+
2845
+ const slot = 12;
2846
+ const ts = 12;
2847
+ const oracle = {
2848
+ price: vBid.add(vAsk).div(new BN(2)), // 11.5
2849
+ slot: new BN(slot),
2850
+ confidence: new BN(1),
2851
+ hasSufficientNumberOfDataPoints: true,
2852
+ };
2853
+
2854
+ // insert some floating limit buy below vAMM bid
2855
+ insertOrderToDLOB(
2856
+ dlob,
2857
+ user0.publicKey,
2858
+ OrderType.LIMIT,
2859
+ MarketType.PERP,
2860
+ 2, // orderId
2861
+ marketIndex,
2862
+ new BN(17).mul(PRICE_PRECISION), // price, crosses vAsk
2863
+ BASE_PRECISION, // quantity
2864
+ PositionDirection.LONG,
2865
+ vBid,
2866
+ vAsk,
2867
+ new BN(slot),
2868
+ new BN(200)
2869
+ );
2870
+ insertOrderToDLOB(
2871
+ dlob,
2872
+ user1.publicKey,
2873
+ OrderType.LIMIT,
2874
+ MarketType.PERP,
2875
+ 3, // orderId
2876
+ marketIndex,
2877
+ new BN(19).mul(PRICE_PRECISION), // price; crosses vAsk
2878
+ new BN(1).mul(BASE_PRECISION), // quantity
2879
+ PositionDirection.LONG,
2880
+ vBid,
2881
+ vAsk,
2882
+ new BN(slot),
2883
+ new BN(200)
2884
+ );
2885
+ insertOrderToDLOB(
2886
+ dlob,
2887
+ user2.publicKey,
2888
+ OrderType.LIMIT,
2889
+ MarketType.PERP,
2890
+ 1, // orderId
2891
+ marketIndex,
2892
+ new BN(5).mul(PRICE_PRECISION), // price; doens't cross
2893
+ BASE_PRECISION, // quantity
2894
+ PositionDirection.LONG,
2895
+ vBid,
2896
+ vAsk,
2897
+ new BN(slot),
2898
+ new BN(200)
2899
+ );
2900
+ console.log(`Book state before fill:`);
2901
+ printBookState(dlob, marketIndex, vBid, vAsk, slot, oracle);
2902
+
2903
+ const nodesToFillBefore = dlob.findNodesToFill(
2904
+ marketIndex,
2905
+ vBid,
2906
+ vAsk,
2907
+ slot,
2908
+ ts,
2909
+ MarketType.PERP,
2910
+ oracle
2911
+ );
2912
+ expect(nodesToFillBefore.length).to.equal(0);
2913
+
2914
+ // auction ends now
2915
+ const afterAuctionSlot = 10 * slot;
2916
+ const afterAuctionTs = 10 * ts;
2917
+
2918
+ const nodesToFillAfter = dlob.findNodesToFill(
2919
+ marketIndex,
2920
+ vBid,
2921
+ vAsk,
2922
+ afterAuctionSlot,
2923
+ afterAuctionTs,
2924
+ MarketType.PERP,
2925
+ oracle
2926
+ );
2927
+
2928
+ console.log(`Book state after fill:`);
2929
+ printBookState(dlob, marketIndex, vBid, vAsk, afterAuctionSlot, oracle);
2930
+
2931
+ console.log(`Filled nodes: ${nodesToFillAfter.length}`);
2932
+ for (const n of nodesToFillAfter) {
2933
+ printCrossedNodes(n, afterAuctionSlot);
2934
+ }
2935
+
2936
+ // taker should fill first order completely with best maker (1/1)
2937
+ expect(
2938
+ nodesToFillAfter[0].node.order?.orderId,
2939
+ 'wrong taker orderId'
2940
+ ).to.equal(3);
2941
+ expect(
2942
+ nodesToFillAfter[0].makerNode?.order?.orderId,
2943
+ 'wrong maker orderId'
2944
+ ).to.equal(undefined);
2945
+
2946
+ // taker should fill second order completely with vamm
2947
+ expect(
2948
+ nodesToFillAfter[1].node.order?.orderId,
2949
+ 'wrong taker orderId'
2950
+ ).to.equal(2);
2951
+ expect(
2952
+ nodesToFillAfter[1].makerNode?.order?.orderId,
2953
+ 'wrong maker orderId'
2954
+ ).to.equal(undefined);
2955
+
2956
+ expect(nodesToFillAfter.length).to.equal(2);
2957
+ });
2958
+
2959
+ it('Test fills two limit orders better than vAmm', () => {
2960
+ const vAsk = new BN(20).mul(PRICE_PRECISION);
2961
+ const vBid = new BN(5).mul(PRICE_PRECISION);
2962
+
2963
+ const mockUserMap = new MockUserMap();
2964
+ const user0 = Keypair.generate();
2965
+ const user0Auth = Keypair.generate();
2966
+ const user1 = Keypair.generate();
2967
+ const user1Auth = Keypair.generate();
2968
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
2969
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user1Auth.publicKey);
2970
+
2971
+ const dlob = new DLOB(
2972
+ mockPerpMarkets,
2973
+ mockSpotMarkets,
2974
+ mockStateAccount,
2975
+ mockUserMap,
2976
+ false
2977
+ );
2978
+ const marketIndex = 0;
2979
+
2980
+ const slot = 12;
2981
+ const ts = 12;
2982
+ const oracle = {
2983
+ price: vBid.add(vAsk).div(new BN(2)), // 11.5
2984
+ slot: new BN(slot),
2985
+ confidence: new BN(1),
2986
+ hasSufficientNumberOfDataPoints: true,
2987
+ };
2988
+
2989
+ // insert a sell below the bid, but above vBid
2990
+ insertOrderToDLOB(
2991
+ dlob,
2992
+ user0.publicKey,
2993
+ OrderType.LIMIT,
2994
+ MarketType.PERP,
2995
+ 3, // orderId
2996
+ marketIndex,
2997
+ new BN(10).mul(PRICE_PRECISION), // price; crosses bid
2998
+ new BN(1).mul(BASE_PRECISION), // quantity
2999
+ PositionDirection.SHORT,
3000
+ vAsk,
3001
+ vBid,
3002
+ new BN(slot),
3003
+ new BN(200)
3004
+ );
3005
+ // insert a buy above the vBid
3006
+ insertOrderToDLOB(
3007
+ dlob,
3008
+ user1.publicKey,
3009
+ OrderType.LIMIT,
3010
+ MarketType.PERP,
3011
+ 2, // orderId
3012
+ marketIndex,
3013
+ new BN(15).mul(PRICE_PRECISION), // price,
3014
+ new BN(8768).mul(BASE_PRECISION).div(new BN(10000)), // quantity
3015
+ PositionDirection.LONG,
3016
+ vBid,
3017
+ vAsk,
3018
+ new BN(slot + 1), // later order becomes taker
3019
+ new BN(200)
3020
+ );
3021
+
3022
+ console.log(`Book state before fill:`);
3023
+ printBookState(dlob, marketIndex, vBid, vAsk, slot, oracle);
3024
+
3025
+ const nodesToFillBefore = dlob.findNodesToFill(
3026
+ marketIndex,
3027
+ vBid,
3028
+ vAsk,
3029
+ slot,
3030
+ ts,
3031
+ MarketType.PERP,
3032
+ oracle
3033
+ );
3034
+ console.log(`Filled nodes: ${nodesToFillBefore.length}`);
3035
+ for (const n of nodesToFillBefore) {
3036
+ printCrossedNodes(n, slot);
3037
+ }
3038
+ expect(nodesToFillBefore.length).to.equal(1);
3039
+
3040
+ // first order is maker, second is taker
3041
+ expect(
3042
+ nodesToFillBefore[0].node.order?.orderId,
3043
+ 'wrong taker orderId'
3044
+ ).to.equal(2);
3045
+ expect(
3046
+ nodesToFillBefore[0].makerNode?.order?.orderId,
3047
+ 'wrong maker orderId'
3048
+ ).to.equal(3);
3049
+ });
3050
+
3051
+ it('Test will not fill two limit orders by same authority', () => {
3052
+ const vAsk = new BN(20).mul(PRICE_PRECISION);
3053
+ const vBid = new BN(5).mul(PRICE_PRECISION);
3054
+
3055
+ const mockUserMap = new MockUserMap();
3056
+ const user0 = Keypair.generate();
3057
+ const user0Auth = Keypair.generate();
3058
+ const user1 = Keypair.generate();
3059
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
3060
+ mockUserMap.addUserAccountAuthority(user1.publicKey, user0Auth.publicKey);
3061
+
3062
+ const dlob = new DLOB(
3063
+ mockPerpMarkets,
3064
+ mockSpotMarkets,
3065
+ mockStateAccount,
3066
+ mockUserMap,
3067
+ false
3068
+ );
3069
+ const marketIndex = 0;
3070
+
3071
+ const slot = 12;
3072
+ const ts = 12;
3073
+ const oracle = {
3074
+ price: vBid.add(vAsk).div(new BN(2)), // 11.5
3075
+ slot: new BN(slot),
3076
+ confidence: new BN(1),
3077
+ hasSufficientNumberOfDataPoints: true,
3078
+ };
3079
+
3080
+ // insert a sell below the bid, but above vBid
3081
+ insertOrderToDLOB(
3082
+ dlob,
3083
+ user0.publicKey,
3084
+ OrderType.LIMIT,
3085
+ MarketType.PERP,
3086
+ 3, // orderId
3087
+ marketIndex,
3088
+ new BN(10).mul(PRICE_PRECISION), // price; crosses bid
3089
+ new BN(1).mul(BASE_PRECISION), // quantity
3090
+ PositionDirection.SHORT,
3091
+ vAsk,
3092
+ vBid,
3093
+ new BN(slot),
3094
+ new BN(200)
3095
+ );
3096
+ // insert a buy above the vBid
3097
+ insertOrderToDLOB(
3098
+ dlob,
3099
+ user1.publicKey,
3100
+ OrderType.LIMIT,
3101
+ MarketType.PERP,
3102
+ 2, // orderId
3103
+ marketIndex,
3104
+ new BN(15).mul(PRICE_PRECISION), // price,
3105
+ new BN(8768).mul(BASE_PRECISION).div(new BN(10000)), // quantity
3106
+ PositionDirection.LONG,
3107
+ vBid,
3108
+ vAsk,
3109
+ new BN(slot),
3110
+ new BN(200)
3111
+ );
3112
+
3113
+ console.log(`Book state before fill:`);
3114
+ printBookState(dlob, marketIndex, vBid, vAsk, slot, oracle);
3115
+
3116
+ const nodesToFillBefore = dlob.findNodesToFill(
3117
+ marketIndex,
3118
+ vBid,
3119
+ vAsk,
3120
+ slot,
3121
+ ts,
3122
+ MarketType.PERP,
3123
+ oracle
3124
+ );
3125
+ console.log(`Filled nodes: ${nodesToFillBefore.length}`);
3126
+ for (const n of nodesToFillBefore) {
3127
+ printCrossedNodes(n, slot);
3128
+ }
3129
+
3130
+ expect(nodesToFillBefore.length).to.equal(0);
3131
+ });
3132
+ });
3133
+
3134
+ describe('DLOB Spot Tests', () => {
3135
+ it('Test proper bids', () => {
3136
+ const vAsk = new BN(115);
3137
+ const vBid = new BN(100);
3138
+ const mockUserMap = new MockUserMap();
3139
+ const dlob = new DLOB(
3140
+ mockPerpMarkets,
3141
+ mockSpotMarkets,
3142
+ mockStateAccount,
3143
+ mockUserMap,
3144
+ false
3145
+ );
3146
+ const marketIndex = 0;
3147
+
3148
+ const slot = 12;
3149
+ const oracle = {
3150
+ price: vBid.add(vAsk).div(new BN(2)),
3151
+ slot: new BN(slot),
3152
+ confidence: new BN(1),
3153
+ hasSufficientNumberOfDataPoints: true,
3154
+ };
3155
+ const testCases = [
3156
+ {
3157
+ expectedIdx: 3,
3158
+ orderId: 5,
3159
+ price: new BN(0), // will calc 108
3160
+ direction: PositionDirection.LONG,
3161
+ orderType: OrderType.MARKET,
3162
+ },
3163
+ {
3164
+ expectedIdx: 4,
3165
+ orderId: 6,
3166
+ price: new BN(0), // will calc 108
3167
+ direction: PositionDirection.LONG,
3168
+ orderType: OrderType.MARKET,
3169
+ },
3170
+ {
3171
+ expectedIdx: 5,
3172
+ orderId: 7,
3173
+ price: new BN(0), // will calc 108
3174
+ direction: PositionDirection.LONG,
3175
+ orderType: OrderType.MARKET,
3176
+ },
3177
+ {
3178
+ expectedIdx: 0,
3179
+ orderId: 1,
3180
+ price: new BN(110),
3181
+ direction: PositionDirection.LONG,
3182
+ orderType: OrderType.LIMIT,
3183
+ },
3184
+ {
3185
+ expectedIdx: 1,
3186
+ orderId: 2,
3187
+ price: new BN(109),
3188
+ direction: PositionDirection.LONG,
3189
+ orderType: OrderType.LIMIT,
3190
+ },
3191
+ {
3192
+ expectedIdx: 6,
3193
+ orderId: 3,
3194
+ price: new BN(107),
3195
+ direction: PositionDirection.LONG,
3196
+ orderType: OrderType.LIMIT,
3197
+ },
3198
+ {
3199
+ expectedIdx: 7,
3200
+ orderId: 4,
3201
+ price: new BN(106),
3202
+ direction: PositionDirection.LONG,
3203
+ orderType: OrderType.LIMIT,
3204
+ },
3205
+ ];
3206
+
3207
+ for (const t of testCases) {
3208
+ const user0 = Keypair.generate();
3209
+ const user0Auth = Keypair.generate();
3210
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
3211
+
3212
+ insertOrderToDLOB(
3213
+ dlob,
3214
+ user0.publicKey,
3215
+ t.orderType || OrderType.LIMIT,
3216
+ MarketType.SPOT,
3217
+ t.orderId || 0, // orderId
3218
+ marketIndex,
3219
+ t.price || new BN(0), // price
3220
+ BASE_PRECISION, // quantity
3221
+ t.direction || PositionDirection.LONG,
3222
+ vBid,
3223
+ vAsk
3224
+ );
3225
+ }
3226
+
3227
+ const expectedTestCase = testCases.sort((a, b) => {
3228
+ // ascending order
3229
+ return a.expectedIdx - b.expectedIdx;
3230
+ });
3231
+ const bids = dlob.getBids(
3232
+ marketIndex,
3233
+ undefined,
3234
+ slot,
3235
+ MarketType.SPOT,
3236
+ oracle
3237
+ );
3238
+
3239
+ console.log('The Book Bids:');
3240
+ let countBids = 0;
3241
+ for (const bid of bids) {
3242
+ printOrderNode(bid, oracle, slot);
3243
+
3244
+ expect(bid.order?.orderId).to.equal(expectedTestCase[countBids].orderId);
3245
+ expect(bid.order?.price.toNumber()).to.equal(
3246
+ expectedTestCase[countBids].price?.toNumber()
3247
+ );
3248
+ expect(bid.order?.direction).to.equal(
3249
+ expectedTestCase[countBids].direction
3250
+ );
3251
+ expect(bid.order?.orderType).to.equal(
3252
+ expectedTestCase[countBids].orderType
3253
+ );
3254
+ countBids++;
3255
+ }
3256
+
3257
+ expect(countBids).to.equal(testCases.length);
3258
+ });
3259
+
3260
+ it('Test proper bids on multiple markets', () => {
3261
+ const vAsk = new BN(15);
3262
+ const vBid = new BN(10);
3263
+ const mockUserMap = new MockUserMap();
3264
+ const dlob = new DLOB(
3265
+ mockPerpMarkets,
3266
+ mockSpotMarkets,
3267
+ mockStateAccount,
3268
+ mockUserMap,
3269
+ false
3270
+ );
3271
+ const marketIndex0 = 0;
3272
+ const marketIndex1 = 1;
3273
+
3274
+ const slot = 12;
3275
+ const oracle = {
3276
+ price: vBid.add(vAsk).div(new BN(2)),
3277
+ slot: new BN(slot),
3278
+ confidence: new BN(1),
3279
+ hasSufficientNumberOfDataPoints: true,
3280
+ };
3281
+ const testCases = [
3282
+ {
3283
+ expectedIdx: 3,
3284
+ orderId: 5,
3285
+ price: new BN(0),
3286
+ direction: PositionDirection.LONG,
3287
+ orderType: OrderType.MARKET,
3288
+ marketIndex: marketIndex0,
3289
+ },
3290
+ {
3291
+ expectedIdx: 4,
3292
+ orderId: 6,
3293
+ price: new BN(0),
3294
+ direction: PositionDirection.LONG,
3295
+ orderType: OrderType.MARKET,
3296
+ marketIndex: marketIndex0,
3297
+ },
3298
+ {
3299
+ expectedIdx: 5,
3300
+ orderId: 7,
3301
+ price: new BN(0),
3302
+ direction: PositionDirection.LONG,
3303
+ orderType: OrderType.MARKET,
3304
+ marketIndex: marketIndex1,
3305
+ },
3306
+ {
3307
+ expectedIdx: 0,
3308
+ orderId: 1,
3309
+ price: new BN(12),
3310
+ direction: PositionDirection.LONG,
3311
+ orderType: OrderType.LIMIT,
3312
+ marketIndex: marketIndex0,
3313
+ },
3314
+ {
3315
+ expectedIdx: 1,
3316
+ orderId: 2,
3317
+ price: new BN(11),
3318
+ direction: PositionDirection.LONG,
3319
+ orderType: OrderType.LIMIT,
3320
+ marketIndex: marketIndex0,
3321
+ },
3322
+ {
3323
+ expectedIdx: 7,
3324
+ orderId: 3,
3325
+ price: new BN(8),
3326
+ direction: PositionDirection.LONG,
3327
+ orderType: OrderType.LIMIT,
3328
+ marketIndex: marketIndex0,
3329
+ },
3330
+ {
3331
+ expectedIdx: 6,
3332
+ orderId: 4,
3333
+ price: new BN(9),
3334
+ direction: PositionDirection.LONG,
3335
+ orderType: OrderType.LIMIT,
3336
+ marketIndex: marketIndex1,
3337
+ },
3338
+ ];
3339
+
3340
+ for (const t of testCases) {
3341
+ const user0 = Keypair.generate();
3342
+ const user0Auth = Keypair.generate();
3343
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
3344
+
3345
+ insertOrderToDLOB(
3346
+ dlob,
3347
+ user0.publicKey,
3348
+ t.orderType || OrderType.LIMIT,
3349
+ MarketType.SPOT,
3350
+ t.orderId || 0, // orderId
3351
+ t.marketIndex,
3352
+ t.price || new BN(0), // price
3353
+ BASE_PRECISION, // quantity
3354
+ t.direction || PositionDirection.LONG,
3355
+ vBid,
3356
+ vAsk
3357
+ );
3358
+ }
3359
+
3360
+ const bids0 = dlob.getBids(
3361
+ marketIndex0,
3362
+ vBid,
3363
+ slot,
3364
+ MarketType.SPOT,
3365
+ oracle
3366
+ );
3367
+ let countBids0 = 0;
3368
+ for (const bid of bids0) {
3369
+ console.log(
3370
+ ` . vAMMNode? ${bid.isVammNode()}, ${JSON.stringify(
3371
+ bid.order?.orderType
3372
+ )} , ${bid.order?.orderId.toString()} , vammTestgetPRice: ${bid.getPrice(
3373
+ oracle,
3374
+ slot
3375
+ )}, price: ${bid.order?.price.toString()}, quantity: ${bid.order?.baseAssetAmountFilled.toString()}/${bid.order?.baseAssetAmount.toString()}`
3376
+ );
3377
+ countBids0++;
3378
+ }
3379
+ expect(countBids0).to.equal(5);
3380
+
3381
+ const bids1 = dlob.getBids(
3382
+ marketIndex1,
3383
+ vBid,
3384
+ slot,
3385
+ MarketType.SPOT,
3386
+ oracle
3387
+ );
3388
+ let countBids1 = 0;
3389
+ for (const bid of bids1) {
3390
+ console.log(
3391
+ ` . vAMMNode? ${bid.isVammNode()}, ${JSON.stringify(
3392
+ bid.order?.orderType
3393
+ )} , ${bid.order?.orderId.toString()} , vammTestgetPRice: ${bid.getPrice(
3394
+ oracle,
3395
+ slot
3396
+ )}, price: ${bid.order?.price.toString()}, quantity: ${bid.order?.baseAssetAmountFilled.toString()}/${bid.order?.baseAssetAmount.toString()}`
3397
+ );
3398
+
3399
+ countBids1++;
3400
+ }
3401
+ expect(countBids1).to.equal(2);
3402
+ });
3403
+
3404
+ it('Test proper asks', () => {
3405
+ const vAsk = new BN(15);
3406
+ const vBid = new BN(10);
3407
+ const mockUserMap = new MockUserMap();
3408
+ const dlob = new DLOB(
3409
+ mockPerpMarkets,
3410
+ mockSpotMarkets,
3411
+ mockStateAccount,
3412
+ mockUserMap,
3413
+ false
3414
+ );
3415
+ const marketIndex = 0;
3416
+
3417
+ const slot = 12;
3418
+ const oracle = {
3419
+ price: vBid.add(vAsk).div(new BN(2)),
3420
+ slot: new BN(slot),
3421
+ confidence: new BN(1),
3422
+ hasSufficientNumberOfDataPoints: true,
3423
+ };
3424
+ const testCases = [
3425
+ {
3426
+ expectedIdx: 0,
3427
+ orderId: 3,
3428
+ price: new BN(0),
3429
+ direction: PositionDirection.SHORT,
3430
+ orderType: OrderType.MARKET,
3431
+ },
3432
+ {
3433
+ expectedIdx: 1,
3434
+ orderId: 4,
3435
+ price: new BN(0),
3436
+ direction: PositionDirection.SHORT,
3437
+ orderType: OrderType.MARKET,
3438
+ },
3439
+ {
3440
+ expectedIdx: 2,
3441
+ orderId: 5,
3442
+ price: new BN(0),
3443
+ direction: PositionDirection.SHORT,
3444
+ orderType: OrderType.MARKET,
3445
+ },
3446
+ {
3447
+ expectedIdx: 3,
3448
+ orderId: 1,
3449
+ price: new BN(13),
3450
+ direction: PositionDirection.SHORT,
3451
+ orderType: OrderType.LIMIT,
3452
+ },
3453
+ {
3454
+ expectedIdx: 6,
3455
+ orderId: 6,
3456
+ price: new BN(16),
3457
+ direction: PositionDirection.SHORT,
3458
+ orderType: OrderType.LIMIT,
3459
+ },
3460
+ {
3461
+ expectedIdx: 7,
3462
+ orderId: 7,
3463
+ price: new BN(17),
3464
+ direction: PositionDirection.SHORT,
3465
+ orderType: OrderType.LIMIT,
3466
+ },
3467
+ {
3468
+ expectedIdx: 4,
3469
+ orderId: 2,
3470
+ price: new BN(14),
3471
+ direction: PositionDirection.SHORT,
3472
+ orderType: OrderType.LIMIT,
3473
+ },
3474
+ ];
3475
+
3476
+ for (const t of testCases) {
3477
+ const user0 = Keypair.generate();
3478
+ const user0Auth = Keypair.generate();
3479
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
3480
+
3481
+ insertOrderToDLOB(
3482
+ dlob,
3483
+ user0.publicKey,
3484
+ t.orderType || OrderType.LIMIT,
3485
+ MarketType.SPOT,
3486
+ t.orderId || 0, // orderId
3487
+ marketIndex,
3488
+ t.price || new BN(0), // price
3489
+ BASE_PRECISION, // quantity
3490
+ t.direction || PositionDirection.SHORT,
3491
+ vBid,
3492
+ vAsk
3493
+ );
3494
+ }
3495
+
3496
+ const expectedTestCase = testCases.sort((a, b) => {
3497
+ // ascending order
3498
+ return a.expectedIdx - b.expectedIdx;
3499
+ });
3500
+ const asks = dlob.getAsks(marketIndex, vAsk, slot, MarketType.SPOT, oracle);
3501
+
3502
+ console.log('The Book Asks:');
3503
+ let countAsks = 0;
3504
+ for (const ask of asks) {
3505
+ console.log(
3506
+ ` . vAMMNode? ${ask.isVammNode()}, ${JSON.stringify(
3507
+ ask.order?.orderType
3508
+ )} , ${ask.order?.orderId.toString()} , vammTestgetPRice: ${ask.getPrice(
3509
+ oracle,
3510
+ slot
3511
+ )}, price: ${ask.order?.price.toString()}, quantity: ${ask.order?.baseAssetAmountFilled.toString()}/${ask.order?.baseAssetAmount.toString()}`
3512
+ );
3513
+
3514
+ expect(ask.order?.orderId).to.equal(expectedTestCase[countAsks].orderId);
3515
+ expect(ask.order?.price.toNumber()).to.equal(
3516
+ expectedTestCase[countAsks].price?.toNumber()
3517
+ );
3518
+ expect(ask.order?.direction).to.equal(
3519
+ expectedTestCase[countAsks].direction
3520
+ );
3521
+ expect(ask.order?.orderType).to.equal(
3522
+ expectedTestCase[countAsks].orderType
3523
+ );
3524
+ countAsks++;
3525
+ }
3526
+
3527
+ expect(countAsks).to.equal(testCases.length);
3528
+ });
3529
+
3530
+ it('Test insert market orders', () => {
3531
+ const slot = 12;
3532
+ const vAsk = new BN(110);
3533
+ const vBid = new BN(100);
3534
+ const oracle = {
3535
+ price: vBid.add(vAsk).div(new BN(2)),
3536
+ slot: new BN(slot),
3537
+ confidence: new BN(1),
3538
+ hasSufficientNumberOfDataPoints: true,
3539
+ };
3540
+ const mockUserMap = new MockUserMap();
3541
+ const dlob = new DLOB(
3542
+ mockPerpMarkets,
3543
+ mockSpotMarkets,
3544
+ mockStateAccount,
3545
+ mockUserMap,
3546
+ false
3547
+ );
3548
+ const marketIndex = 0;
3549
+
3550
+ // 3 mkt buys
3551
+ for (let i = 0; i < 3; i++) {
3552
+ const user0 = Keypair.generate();
3553
+ const user0Auth = Keypair.generate();
3554
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
3555
+
3556
+ insertOrderToDLOB(
3557
+ dlob,
3558
+ user0.publicKey,
3559
+ OrderType.MARKET,
3560
+ MarketType.SPOT,
3561
+ i + 1,
3562
+ marketIndex,
3563
+ new BN(0),
3564
+ BASE_PRECISION,
3565
+ PositionDirection.LONG,
3566
+ vBid,
3567
+ vAsk
3568
+ );
3569
+ }
3570
+
3571
+ // 3 mkt sells
3572
+ for (let i = 0; i < 3; i++) {
3573
+ const user0 = Keypair.generate();
3574
+ const user0Auth = Keypair.generate();
3575
+ mockUserMap.addUserAccountAuthority(user0.publicKey, user0Auth.publicKey);
3576
+
3577
+ insertOrderToDLOB(
3578
+ dlob,
3579
+ user0.publicKey,
3580
+ OrderType.MARKET,
3581
+ MarketType.SPOT,
3582
+ i + 1,
3583
+ marketIndex,
3584
+ new BN(0),
3585
+ BASE_PRECISION,
3586
+ PositionDirection.SHORT,
3587
+ vBid,
3588
+ vAsk
3589
+ );
3590
+ }
3591
+
3592
+ let asks = 0;
3593
+ for (const ask of dlob.getAsks(
3594
+ marketIndex,
3595
+ vAsk,
3596
+ 2,
3597
+ MarketType.SPOT,
3598
+ oracle
3599
+ )) {
3600
+ // vamm node is last in asks
3601
+ asks++;
3602
+
3603
+ if (ask.order) {
3604
+ // market orders
3605
+ expect(getVariant(ask.order?.status)).to.equal('open');
3606
+ expect(getVariant(ask.order?.orderType)).to.equal('market');
3607
+ expect(getVariant(ask.order?.direction)).to.equal('short');
3608
+ expect(ask.order?.orderId).to.equal(asks);
3609
+ }
3610
+ }
3611
+ expect(asks).to.equal(3);
3612
+
3613
+ let bids = 0;
3614
+ for (const bid of dlob.getBids(
3615
+ marketIndex,
3616
+ vBid,
3617
+ 2,
3618
+ MarketType.SPOT,
3619
+ oracle
3620
+ )) {
3621
+ // market orders
3622
+ expect(getVariant(bid.order?.status)).to.equal('open');
3623
+ expect(getVariant(bid.order?.orderType)).to.equal('market');
3624
+ expect(getVariant(bid.order?.direction)).to.equal('long');
3625
+ expect(bid.order?.orderId).to.equal(bids + 1);
3626
+ bids++;
3627
+ }
3628
+ expect(bids).to.equal(3); // 3 orders
3629
+ });
3630
+
3631
+ it('Test insert limit orders', () => {
3632
+ const vAsk = new BN(110);
3633
+ const vBid = new BN(100);
3634
+ const slot = 12;
3635
+ const oracle = {
3636
+ price: vBid.add(vAsk).div(new BN(2)),
3637
+ slot: new BN(slot),
3638
+ confidence: new BN(1),
3639
+ hasSufficientNumberOfDataPoints: true,
3640
+ };
3641
+
3642
+ const mockUserMap = new MockUserMap();
3643
+ const user0 = Keypair.generate();
3644
+ const userAuth0 = Keypair.generate();
3645
+ const user1 = Keypair.generate();
3646
+ const userAuth1 = Keypair.generate();
3647
+ const user2 = Keypair.generate();
3648
+ const userAuth2 = Keypair.generate();
3649
+ const user3 = Keypair.generate();
3650
+ const userAuth3 = Keypair.generate();
3651
+ const user4 = Keypair.generate();
3652
+ const userAuth4 = Keypair.generate();
3653
+ const user5 = Keypair.generate();
3654
+ const userAuth5 = Keypair.generate();
3655
+
3656
+ mockUserMap.addUserAccountAuthority(user0.publicKey, userAuth0.publicKey);
3657
+ mockUserMap.addUserAccountAuthority(user1.publicKey, userAuth1.publicKey);
3658
+ mockUserMap.addUserAccountAuthority(user2.publicKey, userAuth2.publicKey);
3659
+ mockUserMap.addUserAccountAuthority(user3.publicKey, userAuth3.publicKey);
3660
+ mockUserMap.addUserAccountAuthority(user4.publicKey, userAuth4.publicKey);
3661
+ mockUserMap.addUserAccountAuthority(user5.publicKey, userAuth5.publicKey);
3662
+
3663
+ const dlob = new DLOB(
3664
+ mockPerpMarkets,
3665
+ mockSpotMarkets,
3666
+ mockStateAccount,
3667
+ mockUserMap,
3668
+ false
3669
+ );
3670
+ const marketIndex = 0;
3671
+ insertOrderToDLOB(
3672
+ dlob,
3673
+ user0.publicKey,
3674
+ OrderType.LIMIT,
3675
+ MarketType.SPOT,
3676
+ 3, // orderId
3677
+ marketIndex,
3678
+ new BN(50),
3679
+ BASE_PRECISION,
3680
+ PositionDirection.LONG,
3681
+ vBid,
3682
+ vAsk
3683
+ );
3684
+
3685
+ insertOrderToDLOB(
3686
+ dlob,
3687
+ user1.publicKey,
3688
+ OrderType.LIMIT,
3689
+ MarketType.SPOT,
3690
+ 2, // orderId
3691
+ marketIndex,
3692
+ new BN(60),
3693
+ BASE_PRECISION,
3694
+ PositionDirection.LONG,
3695
+ vBid,
3696
+ vAsk
3697
+ );
3698
+
3699
+ insertOrderToDLOB(
3700
+ dlob,
3701
+ user2.publicKey,
3702
+ OrderType.LIMIT,
3703
+ MarketType.SPOT,
3704
+ 1, // orderId
3705
+ marketIndex,
3706
+ new BN(70),
3707
+ BASE_PRECISION,
3708
+ PositionDirection.LONG,
3709
+ vBid,
3710
+ vAsk
3711
+ );
3712
+
3713
+ insertOrderToDLOB(
3714
+ dlob,
3715
+ user3.publicKey,
3716
+ OrderType.LIMIT,
3717
+ MarketType.SPOT,
3718
+ 1, // orderId
3719
+ marketIndex,
3720
+ new BN(120),
3721
+ BASE_PRECISION,
3722
+ PositionDirection.SHORT,
3723
+ vBid,
3724
+ vAsk
3725
+ );
3726
+
3727
+ insertOrderToDLOB(
3728
+ dlob,
3729
+ user4.publicKey,
3730
+ OrderType.LIMIT,
3731
+ MarketType.SPOT,
3732
+ 2, // orderId
3733
+ marketIndex,
3734
+ new BN(130),
3735
+ BASE_PRECISION,
3736
+ PositionDirection.SHORT,
3737
+ vBid,
3738
+ vAsk
3739
+ );
3740
+
3741
+ insertOrderToDLOB(
3742
+ dlob,
3743
+ user5.publicKey,
3744
+ OrderType.LIMIT,
3745
+ MarketType.SPOT,
3746
+ 3, // orderId
3747
+ marketIndex,
3748
+ new BN(140),
3749
+ BASE_PRECISION,
3750
+ PositionDirection.SHORT,
3751
+ vBid,
3752
+ vAsk
3753
+ );
3754
+
3755
+ let asks = 0;
3756
+ for (const ask of dlob.getAsks(
3757
+ marketIndex,
3758
+ vAsk,
3759
+ slot,
3760
+ MarketType.SPOT,
3761
+ oracle
3762
+ )) {
3763
+ if (!ask.order) {
3764
+ console.error('wtf ask vamm?');
3765
+ continue;
3766
+ }
3767
+ // market orders
3768
+ console.log(`ask price: ${ask.order?.price.toString()}`);
3769
+ expect(getVariant(ask.order?.status)).to.equal('open');
3770
+ expect(getVariant(ask.order?.orderType)).to.equal('limit');
3771
+ expect(getVariant(ask.order?.direction)).to.equal('short');
3772
+ expect(ask.order?.orderId).to.equal(asks + 1);
3773
+ expect(ask.order?.price.gt(vAsk)).to.equal(true);
3774
+ asks++;
3775
+ }
3776
+ expect(asks).to.equal(3);
3777
+
3778
+ let bids = 0;
3779
+ for (const bid of dlob.getBids(
3780
+ marketIndex,
3781
+ vBid,
3782
+ slot,
3783
+ MarketType.SPOT,
3784
+ oracle
3785
+ )) {
3786
+ if (!bid.order) {
3787
+ console.error('wtf bid vamm?');
3788
+ continue;
3789
+ }
3790
+ // market orders
3791
+ console.log(`bid price: ${bid.order?.price.toString()}`);
3792
+ expect(getVariant(bid.order?.status)).to.equal('open');
3793
+ expect(getVariant(bid.order?.orderType)).to.equal('limit');
3794
+ expect(getVariant(bid.order?.direction)).to.equal('long');
3795
+ expect(bid.order?.orderId).to.equal(bids + 1);
3796
+ expect(bid.order?.price.lt(vBid)).to.equal(true);
3797
+ bids++;
3798
+ }
3799
+ expect(bids).to.equal(3);
3800
+ });
3801
+
3802
+ it('Test multiple market orders fill with multiple limit orders', () => {
3803
+ const vAsk = new BN(15);
3804
+ const vBid = new BN(10);
3805
+
3806
+ const mockUserMap = new MockUserMap();
3807
+ const user0 = Keypair.generate();
3808
+ const userAuth0 = Keypair.generate();
3809
+ const user1 = Keypair.generate();
3810
+ const userAuth1 = Keypair.generate();
3811
+ const user2 = Keypair.generate();
3812
+ const userAuth2 = Keypair.generate();
3813
+ const user3 = Keypair.generate();
3814
+ const userAuth3 = Keypair.generate();
3815
+ const user4 = Keypair.generate();
3816
+ const userAuth4 = Keypair.generate();
3817
+ const user5 = Keypair.generate();
3818
+ const userAuth5 = Keypair.generate();
3819
+
3820
+ mockUserMap.addUserAccountAuthority(user0.publicKey, userAuth0.publicKey);
3821
+ mockUserMap.addUserAccountAuthority(user1.publicKey, userAuth1.publicKey);
3822
+ mockUserMap.addUserAccountAuthority(user2.publicKey, userAuth2.publicKey);
3823
+ mockUserMap.addUserAccountAuthority(user3.publicKey, userAuth3.publicKey);
3824
+ mockUserMap.addUserAccountAuthority(user4.publicKey, userAuth4.publicKey);
3825
+ mockUserMap.addUserAccountAuthority(user5.publicKey, userAuth5.publicKey);
3826
+
3827
+ const dlob = new DLOB(
3828
+ mockPerpMarkets,
3829
+ mockSpotMarkets,
3830
+ mockStateAccount,
3831
+ mockUserMap,
3832
+ false
3833
+ );
3834
+ const marketIndex = 0;
3835
+
3836
+ // insert some limit buys above vamm bid, below ask
3837
+ insertOrderToDLOB(
3838
+ dlob,
3839
+ user0.publicKey,
3840
+ OrderType.LIMIT,
3841
+ MarketType.SPOT,
3842
+ 1, // orderId
3843
+ marketIndex,
3844
+ new BN(11), // price
3845
+ BASE_PRECISION, // quantity
3846
+ PositionDirection.LONG,
3847
+ vBid,
3848
+ vAsk
3849
+ );
3850
+ insertOrderToDLOB(
3851
+ dlob,
3852
+ user1.publicKey,
3853
+ OrderType.LIMIT,
3854
+ MarketType.SPOT,
3855
+ 2, // orderId
3856
+ marketIndex,
3857
+ new BN(12), // price
3858
+ BASE_PRECISION, // quantity
3859
+ PositionDirection.LONG,
3860
+ vBid,
3861
+ vAsk
3862
+ );
3863
+ insertOrderToDLOB(
3864
+ dlob,
3865
+ user2.publicKey,
3866
+ OrderType.LIMIT,
3867
+ MarketType.SPOT,
3868
+ 3, // orderId
3869
+ marketIndex,
3870
+ new BN(13), // price
3871
+ BASE_PRECISION, // quantity
3872
+ PositionDirection.LONG,
3873
+ vBid,
3874
+ vAsk
3875
+ );
3876
+
3877
+ // should have no crossing orders
3878
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
3879
+ marketIndex,
3880
+ 12, // auction over
3881
+ MarketType.SPOT,
3882
+ {
3883
+ price: vBid.add(vAsk).div(new BN(2)),
3884
+ slot: new BN(12),
3885
+ confidence: new BN(1),
3886
+ hasSufficientNumberOfDataPoints: true,
3887
+ }
3888
+ );
3889
+ expect(nodesToFillBefore.length).to.equal(0);
3890
+
3891
+ // place two market sell order eating 2 of the limit orders
3892
+ insertOrderToDLOB(
3893
+ dlob,
3894
+ user4.publicKey,
3895
+ OrderType.MARKET,
3896
+ MarketType.SPOT,
3897
+ 4, // orderId
3898
+ marketIndex,
3899
+ new BN(12), // price
3900
+ new BN(1).mul(BASE_PRECISION), // quantity
3901
+ PositionDirection.SHORT,
3902
+ vBid,
3903
+ vAsk
3904
+ );
3905
+ insertOrderToDLOB(
3906
+ dlob,
3907
+ user5.publicKey,
3908
+ OrderType.MARKET,
3909
+ MarketType.SPOT,
3910
+ 5, // orderId
3911
+ marketIndex,
3912
+ new BN(12), // price
3913
+ new BN(1).mul(BASE_PRECISION), // quantity
3914
+ PositionDirection.SHORT,
3915
+ vBid,
3916
+ vAsk
3917
+ );
3918
+
3919
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
3920
+ marketIndex,
3921
+ 12, // auction over
3922
+ MarketType.SPOT,
3923
+ {
3924
+ price: vBid.add(vAsk).div(new BN(2)),
3925
+ slot: new BN(12),
3926
+ confidence: new BN(1),
3927
+ hasSufficientNumberOfDataPoints: true,
3928
+ }
3929
+ );
3930
+ for (const n of nodesToFillAfter) {
3931
+ console.log(
3932
+ `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()}`
3933
+ );
3934
+ }
3935
+ expect(nodesToFillAfter.length).to.equal(2);
3936
+
3937
+ // first taker should fill with best maker
3938
+ expect(nodesToFillAfter[0].node.order?.orderId).to.equal(4);
3939
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId).to.equal(3);
3940
+
3941
+ // second taker should fill with second best maker
3942
+ expect(nodesToFillAfter[1].node.order?.orderId).to.equal(5);
3943
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId).to.equal(2);
3944
+ });
3945
+
3946
+ it('Test one market order fills two limit orders', () => {
3947
+ const vAsk = new BN(15);
3948
+ const vBid = new BN(10);
3949
+
3950
+ const mockUserMap = new MockUserMap();
3951
+ const user0 = Keypair.generate();
3952
+ const userAuth0 = Keypair.generate();
3953
+ const user1 = Keypair.generate();
3954
+ const userAuth1 = Keypair.generate();
3955
+ const user2 = Keypair.generate();
3956
+ const userAuth2 = Keypair.generate();
3957
+ const user3 = Keypair.generate();
3958
+ const userAuth3 = Keypair.generate();
3959
+
3960
+ mockUserMap.addUserAccountAuthority(user0.publicKey, userAuth0.publicKey);
3961
+ mockUserMap.addUserAccountAuthority(user1.publicKey, userAuth1.publicKey);
3962
+ mockUserMap.addUserAccountAuthority(user2.publicKey, userAuth2.publicKey);
3963
+ mockUserMap.addUserAccountAuthority(user3.publicKey, userAuth3.publicKey);
3964
+
3965
+ const dlob = new DLOB(
3966
+ mockPerpMarkets,
3967
+ mockSpotMarkets,
3968
+ mockStateAccount,
3969
+ mockUserMap,
3970
+ false
3971
+ );
3972
+ const marketIndex = 0;
3973
+
3974
+ // insert some limit sells below vAMM ask, above bid
3975
+ insertOrderToDLOB(
3976
+ dlob,
3977
+ user0.publicKey,
3978
+ OrderType.LIMIT,
3979
+ MarketType.SPOT,
3980
+ 1, // orderId
3981
+ marketIndex,
3982
+ new BN(14), // price
3983
+ BASE_PRECISION, // quantity
3984
+ PositionDirection.SHORT,
3985
+ vBid,
3986
+ vAsk
3987
+ );
3988
+ insertOrderToDLOB(
3989
+ dlob,
3990
+ user1.publicKey,
3991
+ OrderType.LIMIT,
3992
+ MarketType.SPOT,
3993
+ 2, // orderId
3994
+ marketIndex,
3995
+ new BN(12), // price
3996
+ BASE_PRECISION, // quantity
3997
+ PositionDirection.SHORT,
3998
+ vBid,
3999
+ vAsk
4000
+ );
4001
+ insertOrderToDLOB(
4002
+ dlob,
4003
+ user2.publicKey,
4004
+ OrderType.LIMIT,
4005
+ MarketType.SPOT,
4006
+ 3, // orderId
4007
+ marketIndex,
4008
+ new BN(11), // price
4009
+ BASE_PRECISION, // quantity
4010
+ PositionDirection.SHORT,
4011
+ vBid,
4012
+ vAsk
4013
+ );
4014
+
4015
+ // should have no crossing orders
4016
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
4017
+ marketIndex,
4018
+ 12, // auction over
4019
+ MarketType.SPOT,
4020
+ {
4021
+ price: vBid.add(vAsk).div(new BN(2)),
4022
+ slot: new BN(12),
4023
+ confidence: new BN(1),
4024
+ hasSufficientNumberOfDataPoints: true,
4025
+ }
4026
+ );
4027
+ expect(nodesToFillBefore.length).to.equal(0);
4028
+
4029
+ // place one market buy order eating 2 of the limit orders
4030
+ insertOrderToDLOB(
4031
+ dlob,
4032
+ user3.publicKey,
4033
+ OrderType.MARKET,
4034
+ MarketType.SPOT,
4035
+ 4, // orderId
4036
+ marketIndex,
4037
+ new BN(12), // price
4038
+ new BN(2).mul(BASE_PRECISION), // quantity
4039
+ PositionDirection.LONG,
4040
+ vBid,
4041
+ vAsk
4042
+ );
4043
+
4044
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
4045
+ marketIndex,
4046
+ 12, // auction over
4047
+ MarketType.SPOT,
4048
+ {
4049
+ price: vBid.add(vAsk).div(new BN(2)),
4050
+ slot: new BN(12),
4051
+ confidence: new BN(1),
4052
+ hasSufficientNumberOfDataPoints: true,
4053
+ }
4054
+ );
4055
+ for (const n of nodesToFillAfter) {
4056
+ console.log(
4057
+ `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()}`
4058
+ );
4059
+ }
4060
+ expect(nodesToFillAfter.length).to.equal(2);
4061
+
4062
+ // taker should fill completely with best maker
4063
+ expect(nodesToFillAfter[0].node.order?.orderId).to.equal(4);
4064
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId).to.equal(3);
4065
+
4066
+ // taker should fill completely with second best maker
4067
+ expect(nodesToFillAfter[1].node.order?.orderId).to.equal(4);
4068
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId).to.equal(2);
4069
+ });
4070
+
4071
+ it('Test two market orders to fill one limit order', () => {
4072
+ const vAsk = new BN(15);
4073
+ const vBid = new BN(8);
4074
+
4075
+ const mockUserMap = new MockUserMap();
4076
+ const user0 = Keypair.generate();
4077
+ const userAuth0 = Keypair.generate();
4078
+ const user1 = Keypair.generate();
4079
+ const userAuth1 = Keypair.generate();
4080
+ const user2 = Keypair.generate();
4081
+ const userAuth2 = Keypair.generate();
4082
+ const user3 = Keypair.generate();
4083
+ const userAuth3 = Keypair.generate();
4084
+ const user4 = Keypair.generate();
4085
+ const userAuth4 = Keypair.generate();
4086
+
4087
+ mockUserMap.addUserAccountAuthority(user0.publicKey, userAuth0.publicKey);
4088
+ mockUserMap.addUserAccountAuthority(user1.publicKey, userAuth1.publicKey);
4089
+ mockUserMap.addUserAccountAuthority(user2.publicKey, userAuth2.publicKey);
4090
+ mockUserMap.addUserAccountAuthority(user3.publicKey, userAuth3.publicKey);
4091
+ mockUserMap.addUserAccountAuthority(user4.publicKey, userAuth4.publicKey);
4092
+
4093
+ const dlob = new DLOB(
4094
+ mockPerpMarkets,
4095
+ mockSpotMarkets,
4096
+ mockStateAccount,
4097
+ mockUserMap,
4098
+ false
4099
+ );
4100
+ const marketIndex = 0;
4101
+
4102
+ const slot = 12;
4103
+ const oracle = {
4104
+ price: vBid.add(vAsk).div(new BN(2)),
4105
+ slot: new BN(slot),
4106
+ confidence: new BN(1),
4107
+ hasSufficientNumberOfDataPoints: true,
4108
+ };
4109
+
4110
+ // insert some limit sells below vAMM ask, above bid
4111
+ insertOrderToDLOB(
4112
+ dlob,
4113
+ user0.publicKey,
4114
+ OrderType.LIMIT,
4115
+ MarketType.SPOT,
4116
+ 1, // orderId
4117
+ marketIndex,
4118
+ new BN(14), // price
4119
+ BASE_PRECISION, // quantity
4120
+ PositionDirection.SHORT,
4121
+ vBid,
4122
+ vAsk
4123
+ );
4124
+ insertOrderToDLOB(
4125
+ dlob,
4126
+ user1.publicKey,
4127
+ OrderType.LIMIT,
4128
+ MarketType.SPOT,
4129
+ 2, // orderId
4130
+ marketIndex,
4131
+ new BN(13), // price
4132
+ BASE_PRECISION, // quantity
4133
+ PositionDirection.SHORT,
4134
+ vBid,
4135
+ vAsk
4136
+ );
4137
+ insertOrderToDLOB(
4138
+ dlob,
4139
+ user2.publicKey,
4140
+ OrderType.LIMIT,
4141
+ MarketType.SPOT,
4142
+ 3, // orderId
4143
+ marketIndex,
4144
+ new BN(8), // price <-- best price
4145
+ new BN(3).mul(BASE_PRECISION), // quantity
4146
+ PositionDirection.SHORT,
4147
+ vBid,
4148
+ vAsk
4149
+ );
4150
+
4151
+ // should have no crossing orders
4152
+ const nodesToFillBefore = dlob.findCrossingNodesToFill(
4153
+ marketIndex,
4154
+ 12, // auction over
4155
+ MarketType.SPOT,
4156
+ oracle
4157
+ );
4158
+ expect(nodesToFillBefore.length).to.equal(0);
4159
+
4160
+ // place two market buy orders to eat the best ask
4161
+ insertOrderToDLOB(
4162
+ dlob,
4163
+ user3.publicKey,
4164
+ OrderType.MARKET,
4165
+ MarketType.SPOT,
4166
+ 4, // orderId
4167
+ marketIndex,
4168
+ new BN(0), // price
4169
+ new BN(1).mul(BASE_PRECISION), // quantity
4170
+ PositionDirection.LONG,
4171
+ vBid,
4172
+ vAsk
4173
+ );
4174
+ insertOrderToDLOB(
4175
+ dlob,
4176
+ user4.publicKey,
4177
+ OrderType.MARKET,
4178
+ MarketType.SPOT,
4179
+ 5, // orderId
4180
+ marketIndex,
4181
+ new BN(0), // price
4182
+ new BN(2).mul(BASE_PRECISION), // quantity
4183
+ PositionDirection.LONG,
4184
+ vBid,
4185
+ vAsk
4186
+ );
4187
+
4188
+ const nodesToFillAfter = dlob.findCrossingNodesToFill(
4189
+ marketIndex,
4190
+ slot, // auction over
4191
+ MarketType.SPOT,
4192
+ oracle
4193
+ );
4194
+ const mktNodes = dlob.findExpiredNodesToFill(
4195
+ marketIndex,
4196
+ slot,
4197
+ MarketType.SPOT
4198
+ );
4199
+ console.log(`market nodes: ${mktNodes.length}`);
4200
+
4201
+ printBookState(dlob, marketIndex, vBid, vAsk, slot, oracle);
4202
+
4203
+ for (const n of nodesToFillAfter) {
4204
+ console.log(
4205
+ `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()}`
4206
+ );
4207
+ }
4208
+ expect(nodesToFillAfter.length).to.equal(2);
4209
+
4210
+ // taker should fill completely with best maker
4211
+ expect(nodesToFillAfter[0].node.order?.orderId).to.equal(4);
4212
+ expect(nodesToFillAfter[0].makerNode?.order?.orderId).to.equal(3);
4213
+
4214
+ // taker should fill completely with second best maker
4215
+ expect(nodesToFillAfter[1].node.order?.orderId).to.equal(5);
4216
+ expect(nodesToFillAfter[1].makerNode?.order?.orderId).to.equal(3);
4217
+ });
4218
+
4219
+ it('Test trigger orders', () => {
4220
+ const vAsk = new BN(15);
4221
+ const vBid = new BN(8);
4222
+
4223
+ const mockUserMap = new MockUserMap();
4224
+ const user0 = Keypair.generate();
4225
+ const userAuth0 = Keypair.generate();
4226
+ const user1 = Keypair.generate();
4227
+ const userAuth1 = Keypair.generate();
4228
+ const user2 = Keypair.generate();
4229
+ const userAuth2 = Keypair.generate();
4230
+ const user3 = Keypair.generate();
4231
+ const userAuth3 = Keypair.generate();
4232
+ const user4 = Keypair.generate();
4233
+ const userAuth4 = Keypair.generate();
4234
+ const user5 = Keypair.generate();
4235
+ const userAuth5 = Keypair.generate();
4236
+ const user6 = Keypair.generate();
4237
+ const userAuth6 = Keypair.generate();
4238
+ const user7 = Keypair.generate();
4239
+ const userAuth7 = Keypair.generate();
4240
+ const user8 = Keypair.generate();
4241
+ const userAuth8 = Keypair.generate();
4242
+ const user9 = Keypair.generate();
4243
+ const userAuth9 = Keypair.generate();
4244
+ const user10 = Keypair.generate();
4245
+ const userAuth10 = Keypair.generate();
4246
+ const user11 = Keypair.generate();
4247
+ const userAuth11 = Keypair.generate();
4248
+
4249
+ mockUserMap.addUserAccountAuthority(user0.publicKey, userAuth0.publicKey);
4250
+ mockUserMap.addUserAccountAuthority(user1.publicKey, userAuth1.publicKey);
4251
+ mockUserMap.addUserAccountAuthority(user2.publicKey, userAuth2.publicKey);
4252
+ mockUserMap.addUserAccountAuthority(user3.publicKey, userAuth3.publicKey);
4253
+ mockUserMap.addUserAccountAuthority(user4.publicKey, userAuth4.publicKey);
4254
+ mockUserMap.addUserAccountAuthority(user5.publicKey, userAuth5.publicKey);
4255
+ mockUserMap.addUserAccountAuthority(user6.publicKey, userAuth6.publicKey);
4256
+ mockUserMap.addUserAccountAuthority(user7.publicKey, userAuth7.publicKey);
4257
+ mockUserMap.addUserAccountAuthority(user8.publicKey, userAuth8.publicKey);
4258
+ mockUserMap.addUserAccountAuthority(user9.publicKey, userAuth9.publicKey);
4259
+ mockUserMap.addUserAccountAuthority(user10.publicKey, userAuth10.publicKey);
4260
+ mockUserMap.addUserAccountAuthority(user11.publicKey, userAuth11.publicKey);
4261
+
4262
+ const dlob = new DLOB(
4263
+ mockPerpMarkets,
4264
+ mockSpotMarkets,
4265
+ mockStateAccount,
4266
+ mockUserMap,
4267
+ false
4268
+ );
4269
+ const marketIndex = 0;
4270
+
4271
+ const slot = 20;
4272
+ const oracle = {
4273
+ price: vBid.add(vAsk).div(new BN(2)),
4274
+ slot: new BN(slot),
4275
+ confidence: new BN(1),
4276
+ hasSufficientNumberOfDataPoints: true,
4277
+ };
4278
+
4279
+ const orderIdsToTrigger = [1, 2, 3, 4, 5, 6, 7, 8];
4280
+ // const orderIdsToNotTrigger = [9, 10, 11, 12];
4281
+
4282
+ // should trigger limit buy with above condition
4283
+ insertTriggerOrderToDLOB(
4284
+ dlob,
4285
+ user0.publicKey,
4286
+ OrderType.TRIGGER_LIMIT,
4287
+ MarketType.SPOT,
4288
+ 1, //orderId
4289
+ marketIndex, // marketIndex
4290
+ vBid, // price
4291
+ BASE_PRECISION, // baseAssetAmount: BN,
4292
+ PositionDirection.LONG,
4293
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
4294
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
4295
+ vBid,
4296
+ vAsk
4297
+ );
4298
+ // should trigger limit sell with above condition
4299
+ insertTriggerOrderToDLOB(
4300
+ dlob,
4301
+ user1.publicKey,
4302
+ OrderType.TRIGGER_LIMIT,
4303
+ MarketType.SPOT,
4304
+ 2, //orderId
4305
+ marketIndex, // marketIndex
4306
+ vBid, // price
4307
+ BASE_PRECISION, // baseAssetAmount: BN,
4308
+ PositionDirection.SHORT,
4309
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
4310
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
4311
+ vBid,
4312
+ vAsk
4313
+ );
4314
+ // should trigger market buy with above condition
4315
+ insertTriggerOrderToDLOB(
4316
+ dlob,
4317
+ user2.publicKey,
4318
+ OrderType.TRIGGER_MARKET,
4319
+ MarketType.SPOT,
4320
+ 3, //orderId
4321
+ marketIndex, // marketIndex
4322
+ vAsk, // price
4323
+ BASE_PRECISION, // baseAssetAmount: BN,
4324
+ PositionDirection.LONG,
4325
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
4326
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
4327
+ vBid,
4328
+ vAsk
4329
+ );
4330
+ // should trigger market sell with above condition
4331
+ insertTriggerOrderToDLOB(
4332
+ dlob,
4333
+ user3.publicKey,
4334
+ OrderType.TRIGGER_MARKET,
4335
+ MarketType.SPOT,
4336
+ 4, //orderId
4337
+ marketIndex, // marketIndex
4338
+ vBid, // price
4339
+ BASE_PRECISION, // baseAssetAmount: BN,
4340
+ PositionDirection.SHORT,
4341
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
4342
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
4343
+ vBid,
4344
+ vAsk
4345
+ );
4346
+ // should trigger limit buy with below condition
4347
+ insertTriggerOrderToDLOB(
4348
+ dlob,
4349
+ user4.publicKey,
4350
+ OrderType.TRIGGER_LIMIT,
4351
+ MarketType.SPOT,
4352
+ 5, //orderId
4353
+ marketIndex, // marketIndex
4354
+ vBid, // price
4355
+ BASE_PRECISION, // baseAssetAmount: BN,
4356
+ PositionDirection.LONG,
4357
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
4358
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
4359
+ vBid,
4360
+ vAsk
4361
+ );
4362
+ // should trigger limit sell with below condition
4363
+ insertTriggerOrderToDLOB(
4364
+ dlob,
4365
+ user5.publicKey,
4366
+ OrderType.TRIGGER_LIMIT,
4367
+ MarketType.SPOT,
4368
+ 6, //orderId
4369
+ marketIndex, // marketIndex
4370
+ vBid, // price
4371
+ BASE_PRECISION, // baseAssetAmount: BN,
4372
+ PositionDirection.SHORT,
4373
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
4374
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
4375
+ vBid,
4376
+ vAsk
4377
+ );
4378
+ // should trigger market buy with below condition
4379
+ insertTriggerOrderToDLOB(
4380
+ dlob,
4381
+ user6.publicKey,
4382
+ OrderType.TRIGGER_MARKET,
4383
+ MarketType.SPOT,
4384
+ 7, //orderId
4385
+ marketIndex, // marketIndex
4386
+ vBid, // price
4387
+ BASE_PRECISION, // baseAssetAmount: BN,
4388
+ PositionDirection.LONG,
4389
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
4390
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
4391
+ vBid,
4392
+ vAsk
4393
+ );
4394
+ // should trigger market sell with below condition
4395
+ insertTriggerOrderToDLOB(
4396
+ dlob,
4397
+ user7.publicKey,
4398
+ OrderType.TRIGGER_MARKET,
4399
+ MarketType.SPOT,
4400
+ 8, //orderId
4401
+ marketIndex, // marketIndex
4402
+ vBid, // price
4403
+ BASE_PRECISION, // baseAssetAmount: BN,
4404
+ PositionDirection.LONG,
4405
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
4406
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
4407
+ vBid,
4408
+ vAsk
4409
+ );
4410
+
4411
+ // should NOT trigger market sell with above condition
4412
+ insertTriggerOrderToDLOB(
4413
+ dlob,
4414
+ user8.publicKey,
4415
+ OrderType.TRIGGER_MARKET,
4416
+ MarketType.SPOT,
4417
+ 9, //orderId
4418
+ marketIndex, // marketIndex
4419
+ vBid, // price
4420
+ BASE_PRECISION, // baseAssetAmount: BN,
4421
+ PositionDirection.SHORT,
4422
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
4423
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
4424
+ vBid,
4425
+ vAsk
4426
+ );
4427
+ // should NOT trigger market sell with below condition
4428
+ insertTriggerOrderToDLOB(
4429
+ dlob,
4430
+ user9.publicKey,
4431
+ OrderType.TRIGGER_MARKET,
4432
+ MarketType.SPOT,
4433
+ 10, //orderId
4434
+ marketIndex, // marketIndex
4435
+ vBid, // price
4436
+ BASE_PRECISION, // baseAssetAmount: BN,
4437
+ PositionDirection.SHORT,
4438
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
4439
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
4440
+ vBid,
4441
+ vAsk
4442
+ );
4443
+ // should NOT trigger market buy with above condition
4444
+ insertTriggerOrderToDLOB(
4445
+ dlob,
4446
+ user10.publicKey,
4447
+ OrderType.TRIGGER_MARKET,
4448
+ MarketType.SPOT,
4449
+ 11, //orderId
4450
+ marketIndex, // marketIndex
4451
+ vBid, // price
4452
+ BASE_PRECISION, // baseAssetAmount: BN,
4453
+ PositionDirection.LONG,
4454
+ oracle.price.add(new BN(1)), // triggerPrice: BN,
4455
+ OrderTriggerCondition.ABOVE, // triggerCondition: OrderTriggerCondition,
4456
+ vBid,
4457
+ vAsk
4458
+ );
4459
+ // should NOT trigger market buy with below condition
4460
+ insertTriggerOrderToDLOB(
4461
+ dlob,
4462
+ user11.publicKey,
4463
+ OrderType.TRIGGER_MARKET,
4464
+ MarketType.SPOT,
4465
+ 12, //orderId
4466
+ marketIndex, // marketIndex
4467
+ vBid, // price
4468
+ BASE_PRECISION, // baseAssetAmount: BN,
4469
+ PositionDirection.LONG,
4470
+ oracle.price.sub(new BN(1)), // triggerPrice: BN,
4471
+ OrderTriggerCondition.BELOW, // triggerCondition: OrderTriggerCondition,
4472
+ vBid,
4473
+ vAsk
4474
+ );
4475
+
4476
+ const nodesToTrigger = dlob.findNodesToTrigger(
4477
+ marketIndex,
4478
+ slot,
4479
+ oracle.price,
4480
+ MarketType.SPOT
4481
+ );
4482
+ console.log(`nodesToTriggeR: ${nodesToTrigger.length}`);
4483
+ for (const [idx, n] of nodesToTrigger.entries()) {
4484
+ expect(n.node.order?.orderId).to.equal(orderIdsToTrigger[idx]);
4485
+ console.log(`nodeToTrigger: ${n.node.order?.orderId}`);
4486
+ }
4487
+ });
4488
+
4489
+ it('Test will return expired market orders to fill', () => {
4490
+ const vAsk = new BN(15);
4491
+ const vBid = new BN(8);
4492
+
4493
+ const mockUserMap = new MockUserMap();
4494
+ const user0 = Keypair.generate();
4495
+ const userAuth0 = Keypair.generate();
4496
+ const user1 = Keypair.generate();
4497
+ const userAuth1 = Keypair.generate();
4498
+ mockUserMap.addUserAccountAuthority(user0.publicKey, userAuth0.publicKey);
4499
+ mockUserMap.addUserAccountAuthority(user1.publicKey, userAuth1.publicKey);
4500
+
4501
+ const dlob = new DLOB(
4502
+ mockPerpMarkets,
4503
+ mockSpotMarkets,
4504
+ mockStateAccount,
4505
+ mockUserMap,
4506
+ false
4507
+ );
4508
+ const marketIndex = 0;
4509
+
4510
+ const slot = 20;
4511
+ const ts = 20;
4512
+ const maxTs = new BN(30);
4513
+
4514
+ // non crossing bid
4515
+ insertOrderToDLOB(
4516
+ dlob,
4517
+ user0.publicKey,
4518
+ OrderType.MARKET,
4519
+ MarketType.SPOT,
4520
+ 255, // orderId
4521
+ marketIndex,
4522
+ new BN(2), // price, very low, don't cross vamm
4523
+ BASE_PRECISION, // quantity
4524
+ PositionDirection.LONG,
4525
+ vBid,
4526
+ vAsk,
4527
+ new BN(slot),
4528
+ maxTs
4529
+ );
4530
+ insertOrderToDLOB(
4531
+ dlob,
4532
+ user1.publicKey,
4533
+ OrderType.MARKET,
4534
+ MarketType.SPOT,
4535
+ 2, // orderId
4536
+ marketIndex,
4537
+ new BN(30), // price, very high, don't cross vamm
4538
+ BASE_PRECISION, // quantity
4539
+ PositionDirection.SHORT,
4540
+ vAsk,
4541
+ vBid,
4542
+ new BN(slot),
4543
+ maxTs
4544
+ );
4545
+
4546
+ // order auction is not yet complete, and order is not expired.
4547
+ const slot0 = slot;
4548
+ const ts0 = ts;
4549
+ const nodesToFillBefore = dlob.findNodesToFill(
4550
+ marketIndex,
4551
+ vBid,
4552
+ vAsk,
4553
+ slot0,
4554
+ ts0,
4555
+ MarketType.SPOT,
4556
+ {
4557
+ price: vBid.add(vAsk).div(new BN(2)),
4558
+ slot: new BN(slot0),
4559
+ confidence: new BN(1),
4560
+ hasSufficientNumberOfDataPoints: true,
4561
+ }
4562
+ );
4563
+ expect(nodesToFillBefore.length).to.equal(0);
4564
+
4565
+ // should get order to fill after timeInForce
4566
+ const slot1 = slot0 + slot * 2; // overshoots expiry
4567
+ const ts1 = ts + ts * 2;
4568
+ const nodesToFillAfter = dlob.findNodesToFill(
4569
+ marketIndex,
4570
+ vBid,
4571
+ vAsk,
4572
+ slot1,
4573
+ ts1,
4574
+ MarketType.SPOT,
4575
+ {
4576
+ price: vBid.add(vAsk).div(new BN(2)),
4577
+ slot: new BN(slot1),
4578
+ confidence: new BN(1),
4579
+ hasSufficientNumberOfDataPoints: true,
4580
+ }
4581
+ );
4582
+ expect(nodesToFillAfter.length).to.equal(2);
4583
+
4584
+ // check that the nodes have no makers
4585
+ expect(nodesToFillAfter[0].makerNode).to.equal(undefined);
4586
+ expect(nodesToFillAfter[1].makerNode).to.equal(undefined);
4587
+ });
4588
+ });