@drift-labs/sdk
Version:
SDK for Drift Protocol
2,295 lines (2,121 loc) • 163 kB
text/typescript
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