Skip to content

Commit ef83b21

Browse files
authored
fix(dlob): filter signedMsg orders by taking vs resting status (#1700)
* fix(dlob): filter signedMsg orders by taking vs resting status
1 parent 3937df8 commit ef83b21

File tree

3 files changed

+309
-2
lines changed

3 files changed

+309
-2
lines changed

sdk/src/dlob/DLOB.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -989,7 +989,10 @@ export class DLOB {
989989
const generatorList = [
990990
orderLists.market.bid.getGenerator(),
991991
orderLists.takingLimit.bid.getGenerator(),
992-
orderLists.signedMsg.bid.getGenerator(),
992+
this.signedMsgGenerator(
993+
orderLists.signedMsg.bid,
994+
(x: DLOBNode) => !isRestingLimitOrder(x.order, slot)
995+
),
993996
];
994997

995998
yield* this.getBestNode(
@@ -1021,7 +1024,10 @@ export class DLOB {
10211024
const generatorList = [
10221025
orderLists.market.ask.getGenerator(),
10231026
orderLists.takingLimit.ask.getGenerator(),
1024-
orderLists.signedMsg.ask.getGenerator(),
1027+
this.signedMsgGenerator(
1028+
orderLists.signedMsg.ask,
1029+
(x: DLOBNode) => !isRestingLimitOrder(x.order, slot)
1030+
),
10251031
];
10261032

10271033
yield* this.getBestNode(
@@ -1035,6 +1041,17 @@ export class DLOB {
10351041
);
10361042
}
10371043

1044+
protected *signedMsgGenerator(
1045+
signedMsgOrderList: NodeList<'signedMsg'>,
1046+
filter: (x: DLOBNode) => boolean
1047+
): Generator<DLOBNode> {
1048+
for (const signedMsgOrder of signedMsgOrderList.getGenerator()) {
1049+
if (filter(signedMsgOrder)) {
1050+
yield signedMsgOrder;
1051+
}
1052+
}
1053+
}
1054+
10381055
protected *getBestNode(
10391056
generatorList: Array<Generator<DLOBNode>>,
10401057
oraclePriceData: OraclePriceData,
@@ -1119,6 +1136,9 @@ export class DLOB {
11191136
nodeLists.restingLimit.ask.getGenerator(),
11201137
nodeLists.floatingLimit.ask.getGenerator(),
11211138
nodeLists.protectedFloatingLimit.ask.getGenerator(),
1139+
this.signedMsgGenerator(nodeLists.signedMsg.ask, (x: DLOBNode) =>
1140+
isRestingLimitOrder(x.order, slot)
1141+
),
11221142
];
11231143

11241144
yield* this.getBestNode(
@@ -1158,6 +1178,9 @@ export class DLOB {
11581178
nodeLists.restingLimit.bid.getGenerator(),
11591179
nodeLists.floatingLimit.bid.getGenerator(),
11601180
nodeLists.protectedFloatingLimit.bid.getGenerator(),
1181+
this.signedMsgGenerator(nodeLists.signedMsg.bid, (x: DLOBNode) =>
1182+
isRestingLimitOrder(x.order, slot)
1183+
),
11611184
];
11621185

11631186
yield* this.getBestNode(

sdk/tests/dlob/helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export const mockAMM: AMM = {
149149

150150
takerSpeedBumpOverride: 0,
151151
ammSpreadAdjustment: 0,
152+
ammInventorySpreadAdjustment: 0,
152153
};
153154

154155
export const mockPerpMarkets: Array<PerpMarketAccount> = [

sdk/tests/dlob/test.ts

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3859,6 +3859,289 @@ describe('DLOB Perp Tests', () => {
38593859
'wrong maker orderId'
38603860
).to.equal(5);
38613861
});
3862+
3863+
it('DLOB signedMsgOrder filtering - taking vs resting orders', () => {
3864+
const vAsk = new BN(15);
3865+
const vBid = new BN(10);
3866+
const slot = 1;
3867+
const oracle = {
3868+
price: vBid.add(vAsk).div(new BN(2)),
3869+
slot: new BN(slot),
3870+
confidence: new BN(1),
3871+
hasSufficientNumberOfDataPoints: true,
3872+
};
3873+
3874+
const users = [
3875+
Keypair.generate(),
3876+
Keypair.generate(),
3877+
Keypair.generate(),
3878+
Keypair.generate(),
3879+
];
3880+
const dlob = new DLOB();
3881+
const marketIndex = 0;
3882+
const marketType = MarketType.PERP;
3883+
3884+
// Create orders for both directions (LONG for bids, SHORT for asks)
3885+
const directions = [PositionDirection.LONG, PositionDirection.SHORT];
3886+
const orderConfigs = [
3887+
{
3888+
orderId: 1,
3889+
price: 11,
3890+
postOnly: false,
3891+
auctionComplete: false,
3892+
orderType: OrderType.LIMIT,
3893+
}, // taking order 1
3894+
{
3895+
orderId: 2,
3896+
price: 12,
3897+
postOnly: false,
3898+
auctionComplete: false,
3899+
orderType: OrderType.MARKET,
3900+
}, // taking order 2
3901+
{
3902+
orderId: 3,
3903+
price: 13,
3904+
postOnly: true,
3905+
auctionComplete: false,
3906+
orderType: OrderType.LIMIT,
3907+
}, // resting order 1
3908+
{
3909+
orderId: 4,
3910+
price: 14,
3911+
postOnly: false,
3912+
auctionComplete: true,
3913+
orderType: OrderType.LIMIT,
3914+
}, // resting order 2
3915+
];
3916+
3917+
directions.forEach((direction, dirIndex) => {
3918+
orderConfigs.forEach((config, orderIndex) => {
3919+
const orderSlot = config.auctionComplete ? slot - 11 : slot;
3920+
const order: Order = {
3921+
status: OrderStatus.OPEN,
3922+
orderId: config.orderId + dirIndex * 10, // unique orderId per direction
3923+
marketType: MarketType.PERP,
3924+
marketIndex: 0,
3925+
price: new BN(config.price).mul(PRICE_PRECISION),
3926+
baseAssetAmount: BASE_PRECISION,
3927+
direction,
3928+
orderType: config.orderType,
3929+
postOnly: config.postOnly,
3930+
auctionDuration: 10,
3931+
slot: new BN(orderSlot),
3932+
auctionStartPrice: vBid,
3933+
auctionEndPrice: vAsk,
3934+
bitFlags: 1, // isSignedMsg flag
3935+
userOrderId: 0,
3936+
baseAssetAmountFilled: new BN(0),
3937+
quoteAssetAmountFilled: new BN(0),
3938+
quoteAssetAmount: new BN(0),
3939+
reduceOnly: false,
3940+
triggerPrice: new BN(0),
3941+
triggerCondition: OrderTriggerCondition.ABOVE,
3942+
existingPositionDirection: direction,
3943+
immediateOrCancel: false,
3944+
oraclePriceOffset: 0,
3945+
maxTs: ZERO,
3946+
postedSlotTail: 0,
3947+
};
3948+
dlob.insertSignedMsgOrder(
3949+
order,
3950+
users[orderIndex].publicKey.toString(),
3951+
false
3952+
);
3953+
});
3954+
});
3955+
3956+
// Test taking bids - should only include taking orders (1, 2) in LONG direction
3957+
const takingBids = Array.from(
3958+
dlob.getTakingBids(marketIndex, marketType, slot, oracle)
3959+
);
3960+
3961+
const signedMsgTakingBids = takingBids.filter((node) => node.isSignedMsg);
3962+
expect(signedMsgTakingBids.length).to.equal(2);
3963+
expect(signedMsgTakingBids.some((node) => node.order.orderId === 1)).to.be
3964+
.true; // taking order 1
3965+
expect(signedMsgTakingBids.some((node) => node.order.orderId === 2)).to.be
3966+
.true; // taking order 2
3967+
3968+
// Test resting limit bids - should only include resting orders (3, 4) in LONG direction
3969+
const restingBids = Array.from(
3970+
dlob.getRestingLimitBids(marketIndex, slot, marketType, oracle)
3971+
);
3972+
3973+
const signedMsgRestingBids = restingBids.filter((node) => node.isSignedMsg);
3974+
expect(signedMsgRestingBids.length).to.equal(2);
3975+
expect(signedMsgRestingBids.some((node) => node.order.orderId === 3)).to.be
3976+
.true; // resting order 1
3977+
expect(signedMsgRestingBids.some((node) => node.order.orderId === 4)).to.be
3978+
.true; // resting order 2
3979+
3980+
// Test taking asks - should only include taking orders (11, 12) in SHORT direction
3981+
const takingAsks = Array.from(
3982+
dlob.getTakingAsks(marketIndex, marketType, slot, oracle)
3983+
);
3984+
3985+
const signedMsgTakingAsks = takingAsks.filter((node) => node.isSignedMsg);
3986+
expect(signedMsgTakingAsks.length).to.equal(2);
3987+
expect(signedMsgTakingAsks.some((node) => node.order.orderId === 11)).to.be
3988+
.true; // taking order 1
3989+
expect(signedMsgTakingAsks.some((node) => node.order.orderId === 12)).to.be
3990+
.true; // taking order 2
3991+
3992+
// Test resting limit asks - should only include resting orders (13, 14) in SHORT direction
3993+
const restingAsks = Array.from(
3994+
dlob.getRestingLimitAsks(marketIndex, slot, marketType, oracle)
3995+
);
3996+
3997+
const signedMsgRestingAsks = restingAsks.filter((node) => node.isSignedMsg);
3998+
expect(signedMsgRestingAsks.length).to.equal(2);
3999+
expect(signedMsgRestingAsks.some((node) => node.order.orderId === 13)).to.be
4000+
.true; // resting order 1
4001+
expect(signedMsgRestingAsks.some((node) => node.order.orderId === 14)).to.be
4002+
.true; // resting order 2
4003+
});
4004+
4005+
it('DLOB signedMsgOrder filtering - auction completion transition', () => {
4006+
const vAsk = new BN(15);
4007+
const vBid = new BN(10);
4008+
let slot = 1;
4009+
const oracle = {
4010+
price: vBid.add(vAsk).div(new BN(2)),
4011+
slot: new BN(slot),
4012+
confidence: new BN(1),
4013+
hasSufficientNumberOfDataPoints: true,
4014+
};
4015+
4016+
const user0 = Keypair.generate();
4017+
const dlob = new DLOB();
4018+
const marketIndex = 0;
4019+
const marketType = MarketType.PERP;
4020+
4021+
// Insert a limit order that starts as taking (in auction) and becomes resting (auction complete)
4022+
const limitOrder: Order = {
4023+
status: OrderStatus.OPEN,
4024+
orderId: 1,
4025+
marketType: MarketType.PERP,
4026+
marketIndex: 0,
4027+
price: new BN(11).mul(PRICE_PRECISION),
4028+
baseAssetAmount: BASE_PRECISION,
4029+
direction: PositionDirection.LONG,
4030+
orderType: OrderType.LIMIT,
4031+
postOnly: false,
4032+
auctionDuration: 10,
4033+
slot: new BN(slot),
4034+
auctionStartPrice: vBid,
4035+
auctionEndPrice: vAsk,
4036+
bitFlags: 1, // isSignedMsg flag
4037+
userOrderId: 0,
4038+
baseAssetAmountFilled: new BN(0),
4039+
quoteAssetAmountFilled: new BN(0),
4040+
quoteAssetAmount: new BN(0),
4041+
reduceOnly: false,
4042+
triggerPrice: new BN(0),
4043+
triggerCondition: OrderTriggerCondition.ABOVE,
4044+
existingPositionDirection: PositionDirection.LONG,
4045+
immediateOrCancel: false,
4046+
oraclePriceOffset: 0,
4047+
maxTs: ZERO,
4048+
postedSlotTail: 0,
4049+
};
4050+
dlob.insertSignedMsgOrder(limitOrder, user0.publicKey.toString(), false);
4051+
4052+
// Initially, the order should be in taking orders (auction not complete)
4053+
let takingBids = Array.from(
4054+
dlob.getTakingBids(marketIndex, marketType, slot, oracle)
4055+
);
4056+
let signedMsgTakingBids = takingBids.filter((node) => node.isSignedMsg);
4057+
expect(signedMsgTakingBids.length).to.equal(1);
4058+
expect(signedMsgTakingBids[0].order.orderId).to.equal(1);
4059+
4060+
let restingBids = Array.from(
4061+
dlob.getRestingLimitBids(marketIndex, slot, marketType, oracle)
4062+
);
4063+
let signedMsgRestingBids = restingBids.filter((node) => node.isSignedMsg);
4064+
expect(signedMsgRestingBids.length).to.equal(0);
4065+
4066+
// After auction duration, the order should move to resting orders
4067+
slot = 12; // slot + auctionDuration + 1
4068+
oracle.slot = new BN(slot);
4069+
4070+
takingBids = Array.from(
4071+
dlob.getTakingBids(marketIndex, marketType, slot, oracle)
4072+
);
4073+
signedMsgTakingBids = takingBids.filter((node) => node.isSignedMsg);
4074+
expect(signedMsgTakingBids.length).to.equal(0);
4075+
4076+
restingBids = Array.from(
4077+
dlob.getRestingLimitBids(marketIndex, slot, marketType, oracle)
4078+
);
4079+
signedMsgRestingBids = restingBids.filter((node) => node.isSignedMsg);
4080+
expect(signedMsgRestingBids.length).to.equal(1);
4081+
expect(signedMsgRestingBids[0].order.orderId).to.equal(1);
4082+
});
4083+
4084+
it('DLOB signedMsgOrder filtering - postOnly orders always resting', () => {
4085+
const vAsk = new BN(15);
4086+
const vBid = new BN(10);
4087+
const slot = 1;
4088+
const oracle = {
4089+
price: vBid.add(vAsk).div(new BN(2)),
4090+
slot: new BN(slot),
4091+
confidence: new BN(1),
4092+
hasSufficientNumberOfDataPoints: true,
4093+
};
4094+
4095+
const user0 = Keypair.generate();
4096+
const dlob = new DLOB();
4097+
const marketIndex = 0;
4098+
const marketType = MarketType.PERP;
4099+
4100+
// Insert a postOnly limit order (should always be resting regardless of auction state)
4101+
const postOnlyOrder: Order = {
4102+
status: OrderStatus.OPEN,
4103+
orderId: 1,
4104+
marketType: MarketType.PERP,
4105+
marketIndex: 0,
4106+
price: new BN(11).mul(PRICE_PRECISION),
4107+
baseAssetAmount: BASE_PRECISION,
4108+
direction: PositionDirection.LONG,
4109+
orderType: OrderType.LIMIT,
4110+
postOnly: true,
4111+
auctionDuration: 10,
4112+
slot: new BN(slot),
4113+
auctionStartPrice: vBid,
4114+
auctionEndPrice: vAsk,
4115+
bitFlags: 1, // isSignedMsg flag
4116+
userOrderId: 0,
4117+
baseAssetAmountFilled: new BN(0),
4118+
quoteAssetAmountFilled: new BN(0),
4119+
quoteAssetAmount: new BN(0),
4120+
reduceOnly: false,
4121+
triggerPrice: new BN(0),
4122+
triggerCondition: OrderTriggerCondition.ABOVE,
4123+
existingPositionDirection: PositionDirection.LONG,
4124+
immediateOrCancel: false,
4125+
oraclePriceOffset: 0,
4126+
maxTs: ZERO,
4127+
postedSlotTail: 0,
4128+
};
4129+
dlob.insertSignedMsgOrder(postOnlyOrder, user0.publicKey.toString(), false);
4130+
4131+
// PostOnly orders should always be in resting orders, never in taking orders
4132+
const takingBids = Array.from(
4133+
dlob.getTakingBids(marketIndex, marketType, slot, oracle)
4134+
);
4135+
const signedMsgTakingBids = takingBids.filter((node) => node.isSignedMsg);
4136+
expect(signedMsgTakingBids.length).to.equal(0);
4137+
4138+
const restingBids = Array.from(
4139+
dlob.getRestingLimitBids(marketIndex, slot, marketType, oracle)
4140+
);
4141+
const signedMsgRestingBids = restingBids.filter((node) => node.isSignedMsg);
4142+
expect(signedMsgRestingBids.length).to.equal(1);
4143+
expect(signedMsgRestingBids[0].order.orderId).to.equal(1);
4144+
});
38624145
});
38634146

38644147
describe('DLOB Spot Tests', () => {

0 commit comments

Comments
 (0)