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

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