UNPKG

@drift-labs/sdk

Version:
2,295 lines (2,121 loc) • 163 kB
import { expect } from 'chai'; import { PublicKey, Keypair } from '@solana/web3.js'; import { getVariant, MarketType, DLOB, BN, BASE_PRECISION, PositionDirection, OrderStatus, OrderType, OrderTriggerCondition, PRICE_PRECISION, DLOBNode, OraclePriceData, NodeToFill, isOrderExpired, Order, isMarketOrder, isLimitOrder, ZERO, convertToNumber, QUOTE_PRECISION, //isVariant, uncrossL2, L2Level, } from '../../src'; import { mockPerpMarkets, mockSpotMarkets, mockStateAccount } from './helpers'; // Returns true if asks are sorted ascending const asksAreSortedAsc = (asks: L2Level[]) => { return asks.every((ask, i) => { if (i === 0) { return true; } return ask.price.gt(asks[i - 1].price); }); }; // Returns true if asks are sorted descending const bidsAreSortedDesc = (bids: L2Level[]) => { return bids.every((bid, i) => { if (i === 0) { return true; } return bid.price.lt(bids[i - 1].price); }); }; function insertOrderToDLOB( dlob: DLOB, userAccount: PublicKey, orderType: OrderType, marketType: MarketType, orderId: number, marketIndex: number, price: BN, baseAssetAmount: BN, direction: PositionDirection, auctionStartPrice: BN, auctionEndPrice: BN, slot?: BN, maxTs = ZERO, oraclePriceOffset = new BN(0), postOnly = false, auctionDuration = 10 ) { slot = slot || new BN(1); dlob.insertOrder( { status: OrderStatus.OPEN, orderType, marketType, slot, orderId, userOrderId: 0, marketIndex, price, baseAssetAmount, baseAssetAmountFilled: new BN(0), quoteAssetAmountFilled: new BN(0), quoteAssetAmount: new BN(0), direction, reduceOnly: false, triggerPrice: new BN(0), triggerCondition: OrderTriggerCondition.ABOVE, existingPositionDirection: PositionDirection.LONG, postOnly, immediateOrCancel: false, oraclePriceOffset: oraclePriceOffset.toNumber(), auctionDuration, auctionStartPrice, auctionEndPrice, maxTs, bitFlags: 0, postedSlotTail: 0, }, userAccount.toString(), slot.toNumber(), false, baseAssetAmount ); } function insertTriggerOrderToDLOB( dlob: DLOB, userAccount: PublicKey, orderType: OrderType, marketType: MarketType, orderId: number, marketIndex: number, price: BN, baseAssetAmount: BN, direction: PositionDirection, triggerPrice: BN, triggerCondition: OrderTriggerCondition, auctionStartPrice: BN, auctionEndPrice: BN, slot?: BN, maxTs = ZERO, oraclePriceOffset = new BN(0) ) { slot = slot || new BN(1); dlob.insertOrder( { status: OrderStatus.OPEN, orderType, marketType, slot, orderId, userOrderId: 0, marketIndex, price, baseAssetAmount, baseAssetAmountFilled: new BN(0), quoteAssetAmountFilled: new BN(0), quoteAssetAmount: new BN(0), direction, reduceOnly: false, triggerPrice, triggerCondition, existingPositionDirection: PositionDirection.LONG, postOnly: false, immediateOrCancel: true, oraclePriceOffset: oraclePriceOffset.toNumber(), auctionDuration: 10, auctionStartPrice, auctionEndPrice, maxTs, bitFlags: 0, postedSlotTail: 0, }, userAccount.toString(), slot.toNumber(), false, baseAssetAmount ); } function printOrderNode( node: DLOBNode, oracle: OraclePriceData | undefined, slot: number | undefined ) { console.log( ` . vAMMNode? ${node.isVammNode()},\t${ node.order ? getVariant(node.order?.orderType) : '~' } ${node.order ? getVariant(node.order?.direction) : '~'}\t, slot: ${ node.order?.slot.toString() || '~' }, orderId: ${node.order?.orderId.toString() || '~'},\tnode.getPrice: ${ oracle ? node.getPrice(oracle, slot!) : '~' }, node.price: ${node.order?.price.toString() || '~'}, priceOffset: ${ node.order?.oraclePriceOffset.toString() || '~' } quantity: ${node.order?.baseAssetAmountFilled.toString() || '~'}/${ node.order?.baseAssetAmount.toString() || '~' }` ); } function printBookState( dlob: DLOB, marketIndex: number, vBid: BN | undefined, vAsk: BN | undefined, slot: number, oracle: OraclePriceData ) { const askNodes = dlob.getAsks( marketIndex, vAsk, slot, MarketType.PERP, oracle ); let aa = 0; console.log(`Oracle price: ${oracle.price.toNumber()}`); console.log(`asks:`); for (const a of askNodes) { printOrderNode(a, oracle, slot); aa++; } console.log(`Total ask nodes: ${aa}`); const bidNodes = dlob.getBids( marketIndex, vBid, slot, MarketType.PERP, oracle ); let bb = 0; console.log(`bids:`); for (const b of bidNodes) { printOrderNode(b, oracle, slot); bb++; } console.log(`Total bid nodes: ${bb}`); } function printCrossedNodes(n: NodeToFill, slot: number) { console.log( `Cross Found, takerExists: ${n.node.order !== undefined}, makerExists: ${ n.makerNodes !== undefined }` ); console.log( `node: (mkt: ${isMarketOrder(n.node.order!)}, lim: ${isLimitOrder( n.node.order! )})` ); if (n.makerNodes) { for (const makerNode of n.makerNodes) { console.log( `makerNode: (mkt: ${isMarketOrder( makerNode.order! )}, lim: ${isLimitOrder(makerNode.order!)})` ); } } const printOrder = (o: Order) => { console.log( ` orderId: ${o.orderId}, ${getVariant(o.orderType)}, ${getVariant( o.direction )},\texpired: ${isOrderExpired(o, slot)}, postOnly: ${ o.postOnly }, reduceOnly: ${ o.reduceOnly }, price: ${o.price.toString()}, priceOffset: ${o.oraclePriceOffset.toString()}, baseAmtFileld: ${o.baseAssetAmountFilled.toString()}/${o.baseAssetAmount.toString()}` ); }; if (n.node.order) { const t = n.node.order; console.log(`Taker Order:`); printOrder(t); } if (n.makerNodes.length === 0) { console.log(` maker is vAMM node`); } else { for (const m of n.makerNodes) { console.log(`Maker Order:`); printOrder(m.order!); } } } describe('DLOB Tests', () => { it('Fresh DLOB is empty', () => { const dlob = new DLOB(); const vAsk = new BN(11); const vBid = new BN(10); const slot = 12; const oracle = { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(slot), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }; // check perps for (const market of mockPerpMarkets) { let foundAsks = 0; for (const _ask of dlob.getAsks( market.marketIndex, vAsk, slot, MarketType.PERP, oracle )) { foundAsks++; } expect(foundAsks).to.equal(1); let foundBids = 0; for (const _bid of dlob.getBids( market.marketIndex, vBid, 0, MarketType.PERP, oracle )) { foundBids++; } expect(foundBids).to.equal(1); } // check spots for (const market of mockSpotMarkets) { let foundAsks = 0; for (const _ask of dlob.getAsks( market.marketIndex, undefined, 0, MarketType.SPOT, oracle )) { foundAsks++; } expect(foundAsks).to.equal(0); let foundBids = 0; for (const _bid of dlob.getBids( market.marketIndex, undefined, 0, MarketType.SPOT, oracle )) { foundBids++; } expect(foundBids).to.equal(0); } }); it('Can clear DLOB', () => { const vAsk = new BN(15); const vBid = new BN(10); const user0 = Keypair.generate(); const user1 = Keypair.generate(); const user2 = Keypair.generate(); const dlob = new DLOB(); const marketIndex = 0; const slot = 12; const oracle = { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(slot), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }; insertOrderToDLOB( dlob, user0.publicKey, OrderType.LIMIT, MarketType.PERP, 0, // orderId marketIndex, new BN(9), // price BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk ); insertOrderToDLOB( dlob, user1.publicKey, OrderType.LIMIT, MarketType.PERP, 1, // orderId marketIndex, new BN(8), // price BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk ); insertOrderToDLOB( dlob, user2.publicKey, OrderType.LIMIT, MarketType.PERP, 2, // orderId marketIndex, new BN(8), // price BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk ); const bids = dlob.getBids( marketIndex, undefined, slot, MarketType.PERP, oracle ); let b = 0; for (const _bid of bids) { b++; } expect(b).to.equal(3); dlob.clear(); const bids1 = dlob.getBids( marketIndex, undefined, 0, MarketType.PERP, oracle ); bids1.next(); expect(bids1.next().done, 'bid generator should be done').to.equal(true); }); it('DLOB update resting limit orders bids', () => { const vAsk = new BN(15); const vBid = new BN(10); let slot = 1; const oracle = { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(slot), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }; const user0 = Keypair.generate(); const user1 = Keypair.generate(); const user2 = Keypair.generate(); const dlob = new DLOB(); const marketIndex = 0; const marketType = MarketType.PERP; insertOrderToDLOB( dlob, user0.publicKey, OrderType.LIMIT, MarketType.PERP, 1, // orderId marketIndex, new BN(11), // price BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk, new BN(1) ); insertOrderToDLOB( dlob, user1.publicKey, OrderType.LIMIT, MarketType.PERP, 2, // orderId marketIndex, new BN(12), // price BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk, new BN(11) ); insertOrderToDLOB( dlob, user2.publicKey, OrderType.LIMIT, MarketType.PERP, 3, // orderId marketIndex, new BN(13), // price BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk, new BN(21) ); let takingBids = Array.from( dlob.getTakingBids(marketIndex, marketType, slot, oracle) ); expect(takingBids.length).to.equal(3); expect(takingBids[0].order!.orderId).to.equal(1); expect(takingBids[1].order!.orderId).to.equal(2); expect(takingBids[2].order!.orderId).to.equal(3); let restingBids = Array.from( dlob.getRestingLimitBids(marketIndex, slot, marketType, oracle) ); expect(restingBids.length).to.equal(0); slot += 11; takingBids = Array.from( dlob.getTakingBids(marketIndex, marketType, slot, oracle) ); expect(takingBids.length).to.equal(2); expect(takingBids[0].order!.orderId).to.equal(2); expect(takingBids[1].order!.orderId).to.equal(3); restingBids = Array.from( dlob.getRestingLimitBids(marketIndex, slot, marketType, oracle) ); expect(restingBids.length).to.equal(1); expect(restingBids[0].order!.orderId).to.equal(1); slot += 11; takingBids = Array.from( dlob.getTakingBids(marketIndex, marketType, slot, oracle) ); expect(takingBids.length).to.equal(1); expect(takingBids[0].order!.orderId).to.equal(3); restingBids = Array.from( dlob.getRestingLimitBids(marketIndex, slot, marketType, oracle) ); expect(restingBids.length).to.equal(2); expect(restingBids[0].order!.orderId).to.equal(2); expect(restingBids[1].order!.orderId).to.equal(1); slot += 11; takingBids = Array.from( dlob.getTakingBids(marketIndex, marketType, slot, oracle) ); expect(takingBids.length).to.equal(0); restingBids = Array.from( dlob.getRestingLimitBids(marketIndex, slot, marketType, oracle) ); expect(restingBids.length).to.equal(3); expect(restingBids[0].order!.orderId).to.equal(3); expect(restingBids[1].order!.orderId).to.equal(2); expect(restingBids[2].order!.orderId).to.equal(1); }); it('DLOB update resting limit orders asks', () => { const vAsk = new BN(15); const vBid = new BN(10); let slot = 1; const oracle = { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(slot), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }; const user0 = Keypair.generate(); const user1 = Keypair.generate(); const user2 = Keypair.generate(); const dlob = new DLOB(); const marketIndex = 0; const marketType = MarketType.PERP; insertOrderToDLOB( dlob, user0.publicKey, OrderType.LIMIT, MarketType.PERP, 1, // orderId marketIndex, new BN(13), // price BASE_PRECISION, // quantity PositionDirection.SHORT, vBid, vAsk, new BN(1) ); insertOrderToDLOB( dlob, user1.publicKey, OrderType.LIMIT, MarketType.PERP, 2, // orderId marketIndex, new BN(12), // price BASE_PRECISION, // quantity PositionDirection.SHORT, vBid, vAsk, new BN(11) ); insertOrderToDLOB( dlob, user2.publicKey, OrderType.LIMIT, MarketType.PERP, 3, // orderId marketIndex, new BN(11), // price BASE_PRECISION, // quantity PositionDirection.SHORT, vBid, vAsk, new BN(21) ); let takingBids = Array.from( dlob.getTakingAsks(marketIndex, marketType, slot, oracle) ); expect(takingBids.length).to.equal(3); expect(takingBids[0].order!.orderId).to.equal(1); expect(takingBids[1].order!.orderId).to.equal(2); expect(takingBids[2].order!.orderId).to.equal(3); let restingBids = Array.from( dlob.getRestingLimitAsks(marketIndex, slot, marketType, oracle) ); expect(restingBids.length).to.equal(0); slot += 11; takingBids = Array.from( dlob.getTakingAsks(marketIndex, marketType, slot, oracle) ); expect(takingBids.length).to.equal(2); expect(takingBids[0].order!.orderId).to.equal(2); expect(takingBids[1].order!.orderId).to.equal(3); restingBids = Array.from( dlob.getRestingLimitAsks(marketIndex, slot, marketType, oracle) ); expect(restingBids.length).to.equal(1); expect(restingBids[0].order!.orderId).to.equal(1); slot += 11; takingBids = Array.from( dlob.getTakingAsks(marketIndex, marketType, slot, oracle) ); expect(takingBids.length).to.equal(1); expect(takingBids[0].order!.orderId).to.equal(3); restingBids = Array.from( dlob.getRestingLimitAsks(marketIndex, slot, marketType, oracle) ); expect(restingBids.length).to.equal(2); expect(restingBids[0].order!.orderId).to.equal(2); expect(restingBids[1].order!.orderId).to.equal(1); slot += 11; takingBids = Array.from( dlob.getTakingAsks(marketIndex, marketType, slot, oracle) ); expect(takingBids.length).to.equal(0); restingBids = Array.from( dlob.getRestingLimitAsks(marketIndex, slot, marketType, oracle) ); expect(restingBids.length).to.equal(3); expect(restingBids[0].order!.orderId).to.equal(3); expect(restingBids[1].order!.orderId).to.equal(2); expect(restingBids[2].order!.orderId).to.equal(1); }); }); describe('DLOB Perp Tests', () => { it('Test proper bids', () => { const vAsk = new BN(15); const vBid = new BN(10); const dlob = new DLOB(); const marketIndex = 0; const slot = 12; const oracle = { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(slot), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }; const testCases = [ { expectedIdx: 0, isVamm: false, orderId: 5, price: new BN(0), direction: PositionDirection.LONG, orderType: OrderType.MARKET, slot: new BN(0), postOnly: false, }, { expectedIdx: 1, isVamm: false, orderId: 6, price: new BN(0), direction: PositionDirection.LONG, orderType: OrderType.MARKET, slot: new BN(1), postOnly: false, }, { expectedIdx: 2, isVamm: false, orderId: 7, price: new BN(0), direction: PositionDirection.LONG, orderType: OrderType.MARKET, slot: new BN(2), postOnly: false, }, { expectedIdx: 3, isVamm: false, orderId: 1, price: new BN(12), direction: PositionDirection.LONG, orderType: OrderType.LIMIT, slot: new BN(3), postOnly: false, }, { expectedIdx: 4, isVamm: false, orderId: 2, price: new BN(11), direction: PositionDirection.LONG, orderType: OrderType.LIMIT, slot: new BN(4), postOnly: false, }, { expectedIdx: 7, isVamm: false, orderId: 3, price: new BN(8), direction: PositionDirection.LONG, orderType: OrderType.LIMIT, slot: new BN(5), postOnly: true, }, { expectedIdx: 5, isVamm: true, orderId: undefined, price: undefined, direction: undefined, orderType: undefined, slot: undefined, postOnly: false, }, { expectedIdx: 6, isVamm: false, orderId: 4, price: new BN(9), direction: PositionDirection.LONG, orderType: OrderType.LIMIT, slot: new BN(6), postOnly: true, }, ]; for (const t of testCases) { if (t.isVamm) { continue; } const user = Keypair.generate(); insertOrderToDLOB( dlob, user.publicKey, t.orderType || OrderType.LIMIT, MarketType.PERP, t.orderId || 0, // orderId marketIndex, t.price || new BN(0), // price BASE_PRECISION, // quantity t.direction || PositionDirection.LONG, !t.postOnly ? vBid : ZERO, !t.postOnly ? vAsk : ZERO, t.slot, undefined, undefined, t.postOnly ); } const expectedTestCase = testCases.sort((a, b) => { // ascending order return a.expectedIdx - b.expectedIdx; }); const allBids = dlob.getBids( marketIndex, vBid, slot, MarketType.PERP, oracle ); let countBids = 0; for (const bid of allBids) { expect(bid.isVammNode(), `expected vAMM node`).to.be.eq( expectedTestCase[countBids].isVamm ); expect(bid.order?.orderId, `expected orderId`).to.equal( expectedTestCase[countBids].orderId ); expect(bid.order?.price.toNumber(), `expected price`).to.equal( expectedTestCase[countBids].price?.toNumber() ); expect(bid.order?.direction, `expected order direction`).to.equal( expectedTestCase[countBids].direction ); expect(bid.order?.orderType, `expected order type`).to.equal( expectedTestCase[countBids].orderType ); countBids++; } expect(countBids).to.equal(testCases.length); const takingBids = dlob.getTakingBids( marketIndex, MarketType.PERP, slot, oracle ); countBids = 0; for (const bid of takingBids) { expect(bid.isVammNode(), `expected vAMM node`).to.be.eq( expectedTestCase.slice(0, 5)[countBids].isVamm ); expect(bid.order?.orderId, `expected orderId`).to.equal( expectedTestCase.slice(0, 5)[countBids].orderId ); expect(bid.order?.price.toNumber(), `expected price`).to.equal( expectedTestCase.slice(0, 5)[countBids].price?.toNumber() ); expect(bid.order?.direction, `expected order direction`).to.equal( expectedTestCase.slice(0, 5)[countBids].direction ); expect(bid.order?.orderType, `expected order type`).to.equal( expectedTestCase.slice(0, 5)[countBids].orderType ); countBids++; } expect(countBids).to.equal(expectedTestCase.slice(0, 5).length); const limitBids = dlob.getRestingLimitBids( marketIndex, slot, MarketType.PERP, oracle ); countBids = 0; let idx = 0; for (const bid of limitBids) { if (expectedTestCase.slice(5)[idx].isVamm) { idx++; } expect(bid.isVammNode(), `expected vAMM node`).to.be.eq( expectedTestCase.slice(5)[idx].isVamm ); expect(bid.order?.orderId, `expected orderId`).to.equal( expectedTestCase.slice(5)[idx].orderId ); expect(bid.order?.price.toNumber(), `expected price`).to.equal( expectedTestCase.slice(5)[idx].price?.toNumber() ); expect(bid.order?.direction, `expected order direction`).to.equal( expectedTestCase.slice(5)[idx].direction ); expect(bid.order?.orderType, `expected order type`).to.equal( expectedTestCase.slice(5)[idx].orderType ); countBids++; idx++; } expect(countBids).to.equal(expectedTestCase.slice(5).length - 1); // subtract one since test case 5 is vAMM node }); it('Test proper bids on multiple markets', () => { const vAsk = new BN(15); const vBid = new BN(10); const dlob = new DLOB(); const marketIndex0 = 0; const marketIndex1 = 1; const slot = 12; const oracle = { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(slot), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }; const testCases = [ { expectedIdx: 3, isVamm: false, orderId: 5, price: new BN(0), direction: PositionDirection.LONG, orderType: OrderType.MARKET, marketIndex: marketIndex0, }, { expectedIdx: 4, isVamm: false, orderId: 6, price: new BN(0), direction: PositionDirection.LONG, orderType: OrderType.MARKET, marketIndex: marketIndex0, }, { expectedIdx: 5, isVamm: false, orderId: 7, price: new BN(0), direction: PositionDirection.LONG, orderType: OrderType.MARKET, marketIndex: marketIndex1, }, { expectedIdx: 0, isVamm: false, orderId: 1, price: new BN(12), direction: PositionDirection.LONG, orderType: OrderType.LIMIT, marketIndex: marketIndex0, }, { expectedIdx: 1, isVamm: false, orderId: 2, price: new BN(11), direction: PositionDirection.LONG, orderType: OrderType.LIMIT, marketIndex: marketIndex0, }, { expectedIdx: 7, isVamm: false, orderId: 3, price: new BN(8), direction: PositionDirection.LONG, orderType: OrderType.LIMIT, marketIndex: marketIndex0, }, { expectedIdx: 6, isVamm: false, orderId: 4, price: new BN(9), direction: PositionDirection.LONG, orderType: OrderType.LIMIT, marketIndex: marketIndex1, }, ]; for (const t of testCases) { if (t.isVamm) { continue; } const user = Keypair.generate(); insertOrderToDLOB( dlob, user.publicKey, t.orderType || OrderType.LIMIT, MarketType.PERP, t.orderId || 0, // orderId t.marketIndex, t.price || new BN(0), // price BASE_PRECISION, // quantity t.direction || PositionDirection.LONG, vBid, vAsk ); } const bids0 = dlob.getBids( marketIndex0, vBid, slot, MarketType.PERP, oracle ); let countBids0 = 0; for (const bid of bids0) { console.log( ` . vAMMNode? ${bid.isVammNode()}, ${JSON.stringify( bid.order?.orderType )} , ${bid.order?.orderId.toString()} , vammTestgetPRice: ${bid.getPrice( oracle, slot )}, price: ${bid.order?.price.toString()}, quantity: ${bid.order?.baseAssetAmountFilled.toString()}/${bid.order?.baseAssetAmount.toString()}` ); countBids0++; } expect(countBids0).to.equal(6); const bids1 = dlob.getBids( marketIndex1, vBid, slot, MarketType.PERP, oracle ); let countBids1 = 0; for (const bid of bids1) { console.log( ` . vAMMNode? ${bid.isVammNode()}, ${JSON.stringify( bid.order?.orderType )} , ${bid.order?.orderId.toString()} , vammTestgetPRice: ${bid.getPrice( oracle, slot )}, price: ${bid.order?.price.toString()}, quantity: ${bid.order?.baseAssetAmountFilled.toString()}/${bid.order?.baseAssetAmount.toString()}` ); countBids1++; } expect(countBids1).to.equal(3); }); it('Test proper asks', () => { const vAsk = new BN(15); const vBid = new BN(10); const dlob = new DLOB(); const marketIndex = 0; const slot = 12; const oracle = { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(slot), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }; const testCases = [ { expectedIdx: 0, isVamm: false, orderId: 3, price: new BN(0), direction: PositionDirection.SHORT, orderType: OrderType.MARKET, slot: new BN(0), postOnly: false, }, { expectedIdx: 1, isVamm: false, orderId: 4, price: new BN(0), direction: PositionDirection.SHORT, orderType: OrderType.MARKET, slot: new BN(1), postOnly: false, }, { expectedIdx: 2, isVamm: false, orderId: 5, price: new BN(0), direction: PositionDirection.SHORT, orderType: OrderType.MARKET, slot: new BN(2), postOnly: false, }, { expectedIdx: 3, isVamm: false, orderId: 1, price: new BN(13), direction: PositionDirection.SHORT, orderType: OrderType.LIMIT, slot: new BN(3), postOnly: false, }, { expectedIdx: 6, isVamm: false, orderId: 6, price: new BN(16), direction: PositionDirection.SHORT, orderType: OrderType.LIMIT, slot: new BN(4), postOnly: true, }, { expectedIdx: 5, isVamm: true, orderId: undefined, price: undefined, direction: undefined, orderType: undefined, slot: new BN(0), postOnly: false, }, { expectedIdx: 7, isVamm: false, orderId: 7, price: new BN(17), direction: PositionDirection.SHORT, orderType: OrderType.LIMIT, slot: new BN(4), postOnly: true, }, { expectedIdx: 4, isVamm: false, orderId: 2, price: new BN(14), direction: PositionDirection.SHORT, orderType: OrderType.LIMIT, slot: new BN(4), postOnly: true, }, ]; for (const t of testCases) { if (t.isVamm) { continue; } const user = Keypair.generate(); insertOrderToDLOB( dlob, user.publicKey, t.orderType || OrderType.LIMIT, MarketType.PERP, t.orderId || 0, // orderId marketIndex, t.price || new BN(0), // price BASE_PRECISION, // quantity t.direction || PositionDirection.SHORT, !t.postOnly ? vBid : ZERO, !t.postOnly ? vAsk : ZERO, t.slot, undefined, undefined, t.postOnly ); } const expectedTestCase = testCases.sort((a, b) => { // ascending order return a.expectedIdx - b.expectedIdx; }); const asks = dlob.getAsks(marketIndex, vAsk, slot, MarketType.PERP, oracle); let countAsks = 0; for (const ask of asks) { expect(ask.isVammNode()).to.be.eq(expectedTestCase[countAsks].isVamm); expect(ask.order?.orderId).to.equal(expectedTestCase[countAsks].orderId); expect(ask.order?.price.toNumber()).to.equal( expectedTestCase[countAsks].price?.toNumber() ); expect(ask.order?.direction).to.equal( expectedTestCase[countAsks].direction ); expect(ask.order?.orderType).to.equal( expectedTestCase[countAsks].orderType ); countAsks++; } expect(countAsks).to.equal(testCases.length); const takingAsks = dlob.getTakingAsks( marketIndex, MarketType.PERP, slot, oracle ); countAsks = 0; for (const ask of takingAsks) { expect(ask.isVammNode()).to.be.eq( expectedTestCase.slice(0, 4)[countAsks].isVamm ); expect(ask.order?.orderId).to.equal( expectedTestCase.slice(0, 4)[countAsks].orderId ); expect(ask.order?.price.toNumber()).to.equal( expectedTestCase.slice(0, 4)[countAsks].price?.toNumber() ); expect(ask.order?.direction).to.equal( expectedTestCase.slice(0, 4)[countAsks].direction ); expect(ask.order?.orderType).to.equal( expectedTestCase.slice(0, 4)[countAsks].orderType ); countAsks++; } expect(countAsks).to.equal(expectedTestCase.slice(0, 4).length); const limitAsks = dlob.getRestingLimitAsks( marketIndex, slot, MarketType.PERP, oracle ); countAsks = 0; let idx = 0; for (const ask of limitAsks) { if (expectedTestCase.slice(4)[idx].isVamm) { idx++; } expect(ask.isVammNode()).to.be.eq(expectedTestCase.slice(4)[idx].isVamm); expect(ask.order?.orderId).to.equal( expectedTestCase.slice(4)[idx].orderId ); expect(ask.order?.price.toNumber()).to.equal( expectedTestCase.slice(4)[idx].price?.toNumber() ); expect(ask.order?.direction).to.equal( expectedTestCase.slice(4)[idx].direction ); expect(ask.order?.orderType).to.equal( expectedTestCase.slice(4)[idx].orderType ); countAsks++; idx++; } expect(countAsks).to.equal(expectedTestCase.slice(4).length - 1); // subtract one since test case includes vAMM node }); it('Test insert market orders', () => { const vAsk = new BN(11); const vBid = new BN(10); const dlob = new DLOB(); const marketIndex = 0; const oracle = { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(12), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }; // 3 mkt buys for (let i = 0; i < 3; i++) { const user = Keypair.generate(); insertOrderToDLOB( dlob, user.publicKey, OrderType.MARKET, MarketType.PERP, i + 1, marketIndex, new BN(0), BASE_PRECISION, PositionDirection.LONG, vBid, vAsk ); } // 3 mkt sells for (let i = 0; i < 3; i++) { const user = Keypair.generate(); insertOrderToDLOB( dlob, user.publicKey, OrderType.MARKET, MarketType.PERP, i + 1, marketIndex, new BN(0), BASE_PRECISION, PositionDirection.SHORT, vBid, vAsk ); } let asks = 0; for (const ask of dlob.getAsks( marketIndex, vAsk, 2, MarketType.PERP, oracle )) { // vamm node is last in asks asks++; if (ask.order) { // market orders expect(getVariant(ask.order?.status)).to.equal('open'); expect(getVariant(ask.order?.orderType)).to.equal('market'); expect(getVariant(ask.order?.direction)).to.equal('short'); expect(ask.order?.orderId).to.equal(asks); } } expect(asks).to.equal(4); // vamm ask + 3 orders let bids = 0; const expectedBidOrderIds = [1, 2, 3]; for (const bid of dlob.getBids( marketIndex, vBid, 2, MarketType.PERP, oracle )) { bids++; if (bid.isVammNode()) { continue; } // market orders expect(getVariant(bid.order?.status)).to.equal('open'); expect(getVariant(bid.order?.orderType)).to.equal('market'); expect(getVariant(bid.order?.direction)).to.equal('long'); expect(bid.order?.orderId).to.equal(expectedBidOrderIds[bids - 1]); } expect(bids).to.equal(4); // vamm bid + 3 orders }); it('Test insert limit orders', () => { const slot = 12; const vAsk = new BN(11); const vBid = new BN(10); const oracle = { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(slot), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }; const user0 = Keypair.generate(); const user1 = Keypair.generate(); const user2 = Keypair.generate(); const user3 = Keypair.generate(); const user4 = Keypair.generate(); const user5 = Keypair.generate(); const dlob = new DLOB(); const marketIndex = 0; insertOrderToDLOB( dlob, user0.publicKey, OrderType.LIMIT, MarketType.PERP, 3, // orderId marketIndex, new BN(5), BASE_PRECISION, PositionDirection.LONG, vBid, vAsk, undefined, undefined, undefined, undefined, 0 ); insertOrderToDLOB( dlob, user1.publicKey, OrderType.LIMIT, MarketType.PERP, 2, marketIndex, new BN(6), BASE_PRECISION, PositionDirection.LONG, vBid, vAsk, undefined, undefined, undefined, undefined, 0 ); insertOrderToDLOB( dlob, user2.publicKey, OrderType.LIMIT, MarketType.PERP, 1, // orderId marketIndex, new BN(7), BASE_PRECISION, PositionDirection.LONG, vBid, vAsk, undefined, undefined, undefined, undefined, 0 ); insertOrderToDLOB( dlob, user3.publicKey, OrderType.LIMIT, MarketType.PERP, 1, // orderId marketIndex, new BN(12), BASE_PRECISION, PositionDirection.SHORT, vBid, vAsk, undefined, undefined, undefined, undefined, 0 ); insertOrderToDLOB( dlob, user4.publicKey, OrderType.LIMIT, MarketType.PERP, 2, // orderId marketIndex, new BN(13), BASE_PRECISION, PositionDirection.SHORT, vBid, vAsk, undefined, undefined, undefined, undefined, 0 ); insertOrderToDLOB( dlob, user5.publicKey, OrderType.LIMIT, MarketType.PERP, 3, // orderId marketIndex, new BN(14), BASE_PRECISION, PositionDirection.SHORT, vBid, vAsk, undefined, undefined, undefined, undefined, 0 ); let asks = 0; for (const ask of dlob.getAsks( marketIndex, vAsk, 2, MarketType.PERP, oracle )) { if (ask.order) { // market orders console.log(`ask price: ${ask.order.price.toString()}`); expect(getVariant(ask.order?.status)).to.equal('open'); expect(getVariant(ask.order?.orderType)).to.equal('limit'); expect(getVariant(ask.order?.direction)).to.equal('short'); expect(ask.order?.orderId).to.equal(asks); expect(ask.order?.price.gt(vAsk)).to.equal(true); } // vamm node is first for limit asks asks++; } expect(asks).to.equal(4); // vamm ask + 3 orders let bids = 0; for (const bid of dlob.getBids( marketIndex, vBid, 2, MarketType.PERP, oracle )) { if (bids === 0) { // vamm node expect(bid.order).to.equal(undefined); } else { // market orders console.log(`bid price: ${bid.order?.price.toString()}`); expect(getVariant(bid.order?.status)).to.equal('open'); expect(getVariant(bid.order?.orderType)).to.equal('limit'); expect(getVariant(bid.order?.direction)).to.equal('long'); expect(bid.order?.orderId).to.equal(bids); expect(bid.order?.price.lt(vBid)).to.equal(true); } bids++; } expect(bids).to.equal(4); // vamm bid + 3 orders }); it('Test insert floatinglimit orders', () => { const slot = 12; const vAsk = new BN(11).mul(PRICE_PRECISION); const vBid = new BN(10).mul(PRICE_PRECISION); const oracle = { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(slot), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }; const user0 = Keypair.generate(); const user1 = Keypair.generate(); const user2 = Keypair.generate(); const user3 = Keypair.generate(); const user4 = Keypair.generate(); const user5 = Keypair.generate(); const dlob = new DLOB(); const marketIndex = 0; // insert floating bids insertOrderToDLOB( dlob, user0.publicKey, OrderType.LIMIT, MarketType.PERP, 1, // orderId marketIndex, new BN(0), // price BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk, new BN(slot), ZERO, // TiF new BN(-1).mul(PRICE_PRECISION) // oraclePriceOffset ); insertOrderToDLOB( dlob, user1.publicKey, OrderType.LIMIT, MarketType.PERP, 3, // orderId marketIndex, new BN(0), // price BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk, new BN(slot), ZERO, // TiF new BN(-3).mul(PRICE_PRECISION) // oraclePriceOffset ); insertOrderToDLOB( dlob, user2.publicKey, OrderType.LIMIT, MarketType.PERP, 2, // orderId marketIndex, new BN(0), // price BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk, new BN(slot), ZERO, // TiF new BN(-2).mul(PRICE_PRECISION) // oraclePriceOffset ); // insert floating asks insertOrderToDLOB( dlob, user3.publicKey, OrderType.LIMIT, MarketType.PERP, 5, // orderId marketIndex, new BN(0), // price BASE_PRECISION, // quantity PositionDirection.SHORT, vAsk, vBid, new BN(slot), ZERO, // TiF new BN(2).mul(PRICE_PRECISION) // oraclePriceOffset ); insertOrderToDLOB( dlob, user4.publicKey, OrderType.LIMIT, MarketType.PERP, 6, // orderId marketIndex, new BN(0), // price BASE_PRECISION, // quantity PositionDirection.SHORT, vAsk, vBid, new BN(slot), ZERO, // TiF new BN(3).mul(PRICE_PRECISION) // oraclePriceOffset ); insertOrderToDLOB( dlob, user5.publicKey, OrderType.LIMIT, MarketType.PERP, 4, // orderId marketIndex, new BN(0), // price BASE_PRECISION, // quantity PositionDirection.SHORT, vAsk, vBid, new BN(slot), ZERO, // TiF new BN(1).mul(PRICE_PRECISION) // oraclePriceOffset ); // check floating bids console.log(`bids:`); let lastBidPrice = new BN(9999999999999); // very big let bids = 0; for (const bid of dlob.getBids( marketIndex, vBid, slot, MarketType.PERP, oracle )) { printOrderNode(bid, oracle, slot); if (!bid.isVammNode()) { expect(getVariant(bid.order?.status)).to.equal('open'); expect(getVariant(bid.order?.orderType)).to.equal('limit'); expect(getVariant(bid.order?.direction)).to.equal('long'); // price should be getting worse (getting lower) as we iterate const currentPrice = bid.getPrice(oracle, slot); expect( currentPrice.lte(lastBidPrice), `each bid should be lte the last. current: ${currentPrice.toString()}, last: ${lastBidPrice.toString()}` ).to.be.true; } lastBidPrice = bid.getPrice(oracle, slot); bids++; } expect(bids).to.equal(4); // vamm bid + 3 orders // check floating asks console.log(`asks:`); let asks = 0; for (const ask of dlob.getAsks( marketIndex, vAsk, slot, MarketType.PERP, oracle )) { printOrderNode(ask, oracle, slot); if (!ask.isVammNode()) { expect(getVariant(ask.order?.status)).to.equal('open'); expect(getVariant(ask.order?.orderType)).to.equal('limit'); expect(getVariant(ask.order?.direction)).to.equal('short'); // price should be getting worse (getting higher) as we iterate const currentPrice = ask.getPrice(oracle, slot); expect( currentPrice.gte(lastBidPrice), `each ask should be gte the last. current: ${currentPrice.toString()}, last: ${lastBidPrice.toString()}` ).to.be.true; } asks++; } expect(asks).to.equal(4); // vamm ask + 3 orders }); it('Test multiple market orders fill with multiple limit orders', async () => { const vAsk = new BN(15); const vBid = new BN(10); const user0 = Keypair.generate(); const user1 = Keypair.generate(); const user2 = Keypair.generate(); const user3 = Keypair.generate(); const user4 = Keypair.generate(); const dlob = new DLOB(); const marketIndex = 0; // insert some limit buys above vamm bid, below ask insertOrderToDLOB( dlob, user0.publicKey, OrderType.LIMIT, MarketType.PERP, 1, // orderId marketIndex, new BN(11), // price BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk, undefined, undefined, undefined, true ); insertOrderToDLOB( dlob, user1.publicKey, OrderType.LIMIT, MarketType.PERP, 2, // orderId marketIndex, new BN(12), // price BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk, undefined, undefined, undefined, true ); insertOrderToDLOB( dlob, user2.publicKey, OrderType.LIMIT, MarketType.PERP, 3, // orderId marketIndex, new BN(13), // price BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk, undefined, undefined, undefined, true ); // should have no crossing orders const nodesToFillBefore = dlob.findRestingLimitOrderNodesToFill( marketIndex, 12, // auction over MarketType.PERP, { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(12), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }, false, 10, 0, 1, undefined, undefined ); expect(nodesToFillBefore.length).to.equal(0); // place two market sell order eating 2 of the limit orders insertOrderToDLOB( dlob, user3.publicKey, OrderType.MARKET, MarketType.PERP, 4, // orderId marketIndex, new BN(12), // price new BN(1).mul(BASE_PRECISION), // quantity PositionDirection.SHORT, vBid, vAsk ); insertOrderToDLOB( dlob, user4.publicKey, OrderType.MARKET, MarketType.PERP, 5, // orderId marketIndex, new BN(12), // price new BN(1).mul(BASE_PRECISION), // quantity PositionDirection.SHORT, vBid, vAsk ); const nodesToFillAfter = dlob.findNodesToFill( marketIndex, undefined, undefined, 12, // auction over Date.now(), MarketType.PERP, { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(12), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }, mockStateAccount, mockPerpMarkets[marketIndex] ); console.log(`Filled nodes: ${nodesToFillAfter.length}`); for (const n of nodesToFillAfter) { printCrossedNodes(n, 12); } expect(nodesToFillAfter.length).to.equal(2); // first taker should fill with best maker expect(nodesToFillAfter[0].node.order?.orderId).to.equal(4); expect(nodesToFillAfter[0].makerNodes[0]?.order?.orderId).to.equal(3); // second taker should fill with second best maker expect(nodesToFillAfter[1].node.order?.orderId).to.equal(5); expect(nodesToFillAfter[1].makerNodes[0]?.order?.orderId).to.equal(2); }); it('Test one market orders fills two limit orders', () => { const vAsk = new BN(15); const vBid = new BN(10); const user0 = Keypair.generate(); const user1 = Keypair.generate(); const user2 = Keypair.generate(); const user3 = Keypair.generate(); const dlob = new DLOB(); const marketIndex = 0; // insert some limit sells below vAMM ask, above bid insertOrderToDLOB( dlob, user0.publicKey, OrderType.LIMIT, MarketType.PERP, 1, // orderId marketIndex, new BN(14), // price BASE_PRECISION, // quantity PositionDirection.SHORT, vBid, vAsk, undefined, undefined, undefined, true ); insertOrderToDLOB( dlob, user1.publicKey, OrderType.LIMIT, MarketType.PERP, 2, // orderId marketIndex, new BN(13), // price BASE_PRECISION, // quantity PositionDirection.SHORT, vBid, vAsk, undefined, undefined, undefined, true ); insertOrderToDLOB( dlob, user2.publicKey, OrderType.LIMIT, MarketType.PERP, 3, // orderId marketIndex, new BN(12), // price BASE_PRECISION, // quantity PositionDirection.SHORT, vBid, vAsk, undefined, undefined, undefined, true ); // auction over const endSlot = 12; // should have no crossing orders const nodesToFillBefore = dlob.findRestingLimitOrderNodesToFill( marketIndex, endSlot, MarketType.PERP, { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(endSlot), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }, false, 10, 0, 1, undefined, undefined ); expect(nodesToFillBefore.length).to.equal(0); // place one market buy order eating 2 of the limit orders insertOrderToDLOB( dlob, user3.publicKey, OrderType.MARKET, MarketType.PERP, 4, // orderId marketIndex, new BN(13), // price new BN(2).mul(BASE_PRECISION), // quantity PositionDirection.LONG, vBid, vAsk ); const nodesToFillAfter = dlob.findNodesToFill( marketIndex, undefined, undefined, endSlot, Date.now(), MarketType.PERP, { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(endSlot), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }, mockStateAccount, mockPerpMarkets[marketIndex] ); console.log(`Filled nodes: ${nodesToFillAfter.length}`); for (const n of nodesToFillAfter) { printCrossedNodes(n, endSlot); } expect(nodesToFillAfter.length).to.equal(1); // taker should fill completely with best maker expect( nodesToFillAfter[0].node.order?.orderId, 'wrong taker orderId' ).to.equal(4); expect( nodesToFillAfter[0].makerNodes[0]?.order?.orderId, 'wrong maker orderId' ).to.equal(3); expect( nodesToFillAfter[0].makerNodes[1]?.order?.orderId, 'wrong maker orderId' ).to.equal(2); }); it('Test two market orders to fill one limit order', () => { const vAsk = new BN(15); const vBid = new BN(8); const user0 = Keypair.generate(); const user1 = Keypair.generate(); const user2 = Keypair.generate(); const user3 = Keypair.generate(); const user4 = Keypair.generate(); const dlob = new DLOB(); const marketIndex = 0; const slot = 12; const oracle = { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(slot), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }; // insert some limit sells below vAMM ask, above bid insertOrderToDLOB( dlob, user0.publicKey, OrderType.LIMIT, MarketType.PERP, 1, // orderId marketIndex, new BN(14), // price BASE_PRECISION, // quantity PositionDirection.SHORT, vBid, vAsk, undefined, undefined, undefined, true ); insertOrderToDLOB( dlob, user1.publicKey, OrderType.LIMIT, MarketType.PERP, 2, // orderId marketIndex, new BN(13), // price BASE_PRECISION, // quantity PositionDirection.SHORT, vBid, vAsk, undefined, undefined, undefined, true ); insertOrderToDLOB( dlob, user2.publicKey, OrderType.LIMIT, MarketType.PERP, 3, // orderId marketIndex, new BN(9), // price <-- best price new BN(3).mul(BASE_PRECISION), // quantity PositionDirection.SHORT, vBid, vAsk, undefined, undefined, undefined, true ); const nodesToFillBefore = dlob.findNodesToFill( marketIndex, undefined, undefined, slot, // auction over Date.now(), MarketType.PERP, oracle, mockStateAccount, mockPerpMarkets[marketIndex] ); expect(nodesToFillBefore.length).to.equal(0); // place two market buy orders to eat the best ask insertOrderToDLOB( dlob, user3.publicKey, OrderType.MARKET, MarketType.PERP, 4, // orderId marketIndex, new BN(0), // price new BN(1).mul(BASE_PRECISION), // quantity PositionDirection.LONG, vBid, vAsk ); insertOrderToDLOB( dlob, user4.publicKey, OrderType.MARKET, MarketType.PERP, 5, // orderId marketIndex, new BN(0), // price new BN(2).mul(BASE_PRECISION), // quantity PositionDirection.LONG, vBid, vAsk ); const nodesToFillAfter = dlob.findNodesToFill( marketIndex, undefined, undefined, slot, // auction over Date.now(), MarketType.PERP, oracle, mockStateAccount, mockPerpMarkets[marketIndex] ); const mktNodes = dlob.findExpiredNodesToFill( marketIndex, slot, MarketType.PERP ); console.log(`market nodes: ${mktNodes.length}`); printBookState(dlob, marketIndex, vBid, vAsk, slot, oracle); console.log(`Filled nodes: ${nodesToFillAfter.length}`); for (const n of nodesToFillAfter) { printCrossedNodes(n, slot); } expect(nodesToFillAfter.length).to.equal(2); // taker should fill completely with best maker expect(nodesToFillAfter[0].node.order?.orderId).to.equal(4); expect(nodesToFillAfter[0].makerNodes[0]?.order?.orderId).to.equal(3); // taker should fill completely with second best maker expect(nodesToFillAfter[1].node.order?.orderId).to.equal(5); expect(nodesToFillAfter[1].makerNodes[0]?.order?.orderId).to.equal(3); }); it('Test post only bid fills against fallback', async () => { const vAsk = new BN(150); const vBid = new BN(100); const user0 = Keypair.generate(); const dlob = new DLOB(); const marketIndex = 0; const makerRebateNumerator = 1; const makerRebateDenominator = 10; // post only bid same as ask insertOrderToDLOB( dlob, user0.publicKey, OrderType.LIMIT, MarketType.PERP, 1, // orderId marketIndex, vAsk, // same price as vAsk BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk, undefined, undefined, undefined, true ); // should have no crossing orders const nodesToFillBefore = dlob.findRestingLimitOrderNodesToFill( marketIndex, 12, // auction over MarketType.PERP, { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(12), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }, false, 10, makerRebateNumerator, makerRebateDenominator, vAsk, vBid ); expect(nodesToFillBefore.length).to.equal(0); // post only bid crosses ask const price = vAsk.add( vAsk.muln(makerRebateNumerator).divn(makerRebateDenominator) ); insertOrderToDLOB( dlob, user0.publicKey, OrderType.LIMIT, MarketType.PERP, 2, // orderId marketIndex, price, // crosses vask BASE_PRECISION, // quantity PositionDirection.LONG, vBid, vAsk, undefined, undefined, undefined, true ); // should have no crossing orders const nodesToFillAfter = dlob.findRestingLimitOrderNodesToFill( marketIndex, 12, // auction over MarketType.PERP, { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(12), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }, false, 10, makerRebateNumerator, makerRebateDenominator, vAsk, vBid ); expect(nodesToFillAfter.length).to.equal(1); }); it('Test post only ask fills against fallback', async () => { const vAsk = new BN(150); const vBid = new BN(100); const user0 = Keypair.generate(); const dlob = new DLOB(); const marketIndex = 0; const makerRebateNumerator = 1; const makerRebateDenominator = 10; // post only bid same as ask insertOrderToDLOB( dlob, user0.publicKey, OrderType.LIMIT, MarketType.PERP, 1, // orderId marketIndex, vBid, // same price as vAsk BASE_PRECISION, // quantity PositionDirection.SHORT, vBid, vAsk, undefined, undefined, undefined, true ); // should have no crossing orders const nodesToFillBefore = dlob.findRestingLimitOrderNodesToFill( marketIndex, 12, // auction over MarketType.PERP, { price: vBid.add(vAsk).div(new BN(2)), slot: new BN(12), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }, false, 10, makerRebateNumerator, makerRebateDenominator, vAsk, vBid ); expect(nodesToFillBefore.length).to.equal(0); // post only bid crosses ask const price = vBid.sub( vAsk.muln(makerRebateNumerator).divn(makerRebateDenominator) ); insertOrderToDLOB( dlob, user0.publicKey, OrderType.LIMIT, MarketType.PERP, 2, // orderId marketIndex, price, // crosses vask