diff --git a/protocol/testutil/constants/stateful_orders.go b/protocol/testutil/constants/stateful_orders.go index 58483bbd06..448c846f15 100644 --- a/protocol/testutil/constants/stateful_orders.go +++ b/protocol/testutil/constants/stateful_orders.go @@ -456,6 +456,20 @@ var ( ConditionType: clobtypes.Order_CONDITION_TYPE_STOP_LOSS, ConditionalOrderTriggerSubticks: 20, } + ConditionalOrder_Alice_Num1_Id0_Clob0_Buy5_Price20_GTBT15_StopLoss20 = clobtypes.Order{ + OrderId: clobtypes.OrderId{ + SubaccountId: Alice_Num1, + ClientId: 0, + OrderFlags: clobtypes.OrderIdFlags_Conditional, + ClobPairId: 0, + }, + Side: clobtypes.Order_SIDE_BUY, + Quantums: 5, + Subticks: 20, + GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 15}, + ConditionType: clobtypes.Order_CONDITION_TYPE_STOP_LOSS, + ConditionalOrderTriggerSubticks: 20, + } ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_TakeProfit20 = clobtypes.Order{ OrderId: clobtypes.OrderId{ SubaccountId: Alice_Num0, @@ -554,6 +568,20 @@ var ( ConditionType: clobtypes.Order_CONDITION_TYPE_STOP_LOSS, ConditionalOrderTriggerSubticks: 25, } + ConditionalOrder_Alice_Num1_Id1_Clob0_Buy15_Price25_GTBT15_StopLoss25 = clobtypes.Order{ + OrderId: clobtypes.OrderId{ + SubaccountId: Alice_Num1, + ClientId: 1, + OrderFlags: clobtypes.OrderIdFlags_Conditional, + ClobPairId: 0, + }, + Side: clobtypes.Order_SIDE_BUY, + Quantums: 15, + Subticks: 25, + GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 15}, + ConditionType: clobtypes.Order_CONDITION_TYPE_STOP_LOSS, + ConditionalOrderTriggerSubticks: 25, + } ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price10_GTBT15_TakeProfit20 = clobtypes.Order{ OrderId: clobtypes.OrderId{ SubaccountId: Alice_Num0, @@ -624,6 +652,20 @@ var ( ConditionType: clobtypes.Order_CONDITION_TYPE_TAKE_PROFIT, ConditionalOrderTriggerSubticks: 20, } + ConditionalOrder_Alice_Num1_Id2_Clob0_Sell20_Price20_GTBT15_TakeProfit20 = clobtypes.Order{ + OrderId: clobtypes.OrderId{ + SubaccountId: Alice_Num1, + ClientId: 2, + OrderFlags: clobtypes.OrderIdFlags_Conditional, + ClobPairId: 0, + }, + Side: clobtypes.Order_SIDE_SELL, + Quantums: 20, + Subticks: 20, + GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 15}, + ConditionType: clobtypes.Order_CONDITION_TYPE_TAKE_PROFIT, + ConditionalOrderTriggerSubticks: 20, + } ConditionalOrder_Alice_Num0_Id2_Clob0_Buy20_Price10_GTBT15_TakeProfit10 = clobtypes.Order{ OrderId: clobtypes.OrderId{ SubaccountId: Alice_Num0, @@ -666,6 +708,20 @@ var ( ConditionType: clobtypes.Order_CONDITION_TYPE_STOP_LOSS, ConditionalOrderTriggerSubticks: 25, } + ConditionalOrder_Alice_Num1_Id3_Clob0_Buy25_Price25_GTBT15_StopLoss25 = clobtypes.Order{ + OrderId: clobtypes.OrderId{ + SubaccountId: Alice_Num1, + ClientId: 3, + OrderFlags: clobtypes.OrderIdFlags_Conditional, + ClobPairId: 0, + }, + Side: clobtypes.Order_SIDE_BUY, + Quantums: 25, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 15}, + ConditionType: clobtypes.Order_CONDITION_TYPE_STOP_LOSS, + ConditionalOrderTriggerSubticks: 25, + } ConditionalOrder_Alice_Num0_Id3_Clob0_Buy25_Price10_GTBT15_TakeProfit20 = clobtypes.Order{ OrderId: clobtypes.OrderId{ SubaccountId: Alice_Num0, @@ -722,6 +778,20 @@ var ( ConditionType: clobtypes.Order_CONDITION_TYPE_STOP_LOSS, ConditionalOrderTriggerSubticks: 20, } + ConditionalOrder_Alice_Num0_Id1_Clob1_Buy5_Price10_GTBT15_StopLoss20 = clobtypes.Order{ + OrderId: clobtypes.OrderId{ + SubaccountId: Alice_Num0, + ClientId: 0, + OrderFlags: clobtypes.OrderIdFlags_Conditional, + ClobPairId: 1, + }, + Side: clobtypes.Order_SIDE_BUY, + Quantums: 5, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 15}, + ConditionType: clobtypes.Order_CONDITION_TYPE_STOP_LOSS, + ConditionalOrderTriggerSubticks: 20, + } ConditionalOrder_Alice_Num0_Id0_Clob1_Buy5_Price10_GTBT15_TakeProfit20 = clobtypes.Order{ OrderId: clobtypes.OrderId{ SubaccountId: Alice_Num0, diff --git a/protocol/x/clob/abci.go b/protocol/x/clob/abci.go index 4171498a1f..22a5ac8f11 100644 --- a/protocol/x/clob/abci.go +++ b/protocol/x/clob/abci.go @@ -88,28 +88,10 @@ func EndBlocker( ) } - // Prune expired untriggered conditional orders from the in-memory UntriggeredConditionalOrders struct. - keeper.PruneUntriggeredConditionalOrders( - expiredStatefulOrderIds, - processProposerMatchesEvents.PlacedStatefulCancellationOrderIds, - ) - // Update the memstore with expired order ids. // These expired stateful order ids will be purged from the memclob in `Commit`. processProposerMatchesEvents.ExpiredStatefulOrderIds = expiredStatefulOrderIds - // Before triggering conditional orders, add newly-placed conditional orders to the clob keeper's - // in-memory UntriggeredConditionalOrders data structure to allow conditional orders to - // trigger in the same block they are placed. Skip triggering orders which have been cancelled - // or expired. - // TODO(CLOB-773) Support conditional order replacements. Ensure replacements are de-duplicated. - keeper.AddUntriggeredConditionalOrders( - ctx, - processProposerMatchesEvents.PlacedConditionalOrderIds, - lib.UniqueSliceToSet(processProposerMatchesEvents.GetPlacedStatefulCancellationOrderIds()), - lib.UniqueSliceToSet(expiredStatefulOrderIds), - ) - // Poll out all triggered conditional orders from `UntriggeredConditionalOrders` and update state. triggeredConditionalOrderIds := keeper.MaybeTriggerConditionalOrders(ctx) // Update the memstore with conditional order ids triggered in the last block. diff --git a/protocol/x/clob/abci_test.go b/protocol/x/clob/abci_test.go index b62b927f11..44b121c8f9 100644 --- a/protocol/x/clob/abci_test.go +++ b/protocol/x/clob/abci_test.go @@ -64,88 +64,6 @@ func assertFillAmountAndPruneState( } } -func TestEndBlocker_Failure(t *testing.T) { - blockHeight := uint32(5) - tests := map[string]struct { - blockTime time.Time - expiredStatefulOrderIds []types.OrderId - setupState func(ctx sdk.Context, k keepertest.ClobKeepersTestContext, m *mocks.MemClob) - - expectedPanicMessage string - }{ - "Panics if cancelled order ids and expired order ids overlap": { - blockTime: unixTimeFifteen, - expiredStatefulOrderIds: []types.OrderId{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_StopLoss20.OrderId, - }, - setupState: func(ctx sdk.Context, ks keepertest.ClobKeepersTestContext, m *mocks.MemClob) { - expiredOrder := constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_StopLoss20 - - ks.ClobKeeper.SetLongTermOrderPlacement(ctx, expiredOrder, blockHeight) - ks.ClobKeeper.MustAddOrderToStatefulOrdersTimeSlice( - ctx, - expiredOrder.MustGetUnixGoodTilBlockTime(), - expiredOrder.OrderId, - ) - - ks.ClobKeeper.MustSetProcessProposerMatchesEvents( - ctx, - types.ProcessProposerMatchesEvents{ - BlockHeight: blockHeight, - PlacedStatefulCancellationOrderIds: []types.OrderId{ - expiredOrder.OrderId, - }, - }, - ) - }, - expectedPanicMessage: fmt.Sprintf( - "PruneUntriggeredConditionalOrders: duplicate order id %+v in expired and "+ - "cancelled order lists", constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_StopLoss20.OrderId, - ), - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - memClob := &mocks.MemClob{} - memClob.On("SetClobKeeper", mock.Anything).Return() - - mockIndexerEventManager := &mocks.IndexerEventManager{} - - ks := keepertest.NewClobKeepersTestContext(t, memClob, &mocks.BankKeeper{}, mockIndexerEventManager) - ctx := ks.Ctx.WithBlockHeight(int64(blockHeight)).WithBlockTime(tc.blockTime) - - for _, orderId := range tc.expiredStatefulOrderIds { - mockIndexerEventManager.On("AddBlockEvent", - ctx, - indexerevents.SubtypeStatefulOrder, - indexer_manager.IndexerTendermintEvent_BLOCK_EVENT_END_BLOCK, - indexerevents.StatefulOrderEventVersion, - indexer_manager.GetBytes( - indexerevents.NewStatefulOrderRemovalEvent( - orderId, - indexershared.OrderRemovalReason_ORDER_REMOVAL_REASON_EXPIRED, - ), - ), - ).Once().Return() - } - - tc.setupState(ctx, ks, memClob) - - require.PanicsWithValue( - t, - tc.expectedPanicMessage, - func() { - clob.EndBlocker( - ctx, - *ks.ClobKeeper, - ) - }, - ) - }) - } -} - func TestEndBlocker_Success(t *testing.T) { prunedOrderIdOne := types.OrderId{SubaccountId: constants.Alice_Num0, ClientId: 0} prunedOrderIdTwo := types.OrderId{SubaccountId: constants.Alice_Num0, ClientId: 1} @@ -222,117 +140,6 @@ func TestEndBlocker_Success(t *testing.T) { OrderIdsFilledInLastBlock: []types.OrderId{prunedOrderIdTwo, orderIdThree}, }, }, - "Prunes expired and cancelled untriggered conditional orders from UntriggeredConditionalorders": { - blockTime: unixTimeFifteen, - setupState: func(ctx sdk.Context, ks keepertest.ClobKeepersTestContext, m *mocks.MemClob) { - // expired orders - orderToPrune1 := constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_StopLoss20 - orderToPrune2 := constants.ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price25_GTBT15_StopLoss25 - orderToPrune3 := constants.ConditionalOrder_Alice_Num0_Id0_Clob1_Buy5_Price10_GTBT15_TakeProfit20 - // cancelled order - orderToPrune4 := constants.ConditionalOrder_Alice_Num1_Id1_Clob0_Sell50_Price5_GTB30_TakeProfit20 - - // add expired orders to state, cancelled orders already removed in DeliverTx - orders := []types.Order{ - orderToPrune1, - orderToPrune2, - orderToPrune3, - } - for _, order := range orders { - ks.ClobKeeper.SetLongTermOrderPlacement(ctx, order, 0) - ks.ClobKeeper.MustAddOrderToStatefulOrdersTimeSlice( - ctx, - order.MustGetUnixGoodTilBlockTime(), - order.OrderId, - ) - } - - ks.ClobKeeper.UntriggeredConditionalOrders = map[types.ClobPairId]*keeper.UntriggeredConditionalOrders{ - constants.ClobPair_Btc.GetClobPairId(): { - OrdersToTriggerWhenOraclePriceLTETriggerPrice: []types.Order{orderToPrune1, orderToPrune2, orderToPrune4}, - OrdersToTriggerWhenOraclePriceGTETriggerPrice: []types.Order{}, - }, - constants.ClobPair_Eth.GetClobPairId(): { - OrdersToTriggerWhenOraclePriceLTETriggerPrice: []types.Order{}, - OrdersToTriggerWhenOraclePriceGTETriggerPrice: []types.Order{orderToPrune3}, - }, - } - - ks.ClobKeeper.MustSetProcessProposerMatchesEvents( - ctx, - types.ProcessProposerMatchesEvents{ - PlacedStatefulCancellationOrderIds: []types.OrderId{ - orderToPrune4.OrderId, - }, - BlockHeight: blockHeight, - }, - ) - }, - expectedUntriggeredConditionalOrders: map[types.ClobPairId]*keeper.UntriggeredConditionalOrders{}, - expectedStatefulPlacementInState: map[types.OrderId]bool{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_StopLoss20.OrderId: false, - constants.ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price25_GTBT15_StopLoss25.OrderId: false, - constants.ConditionalOrder_Alice_Num0_Id0_Clob1_Buy5_Price10_GTBT15_TakeProfit20.OrderId: false, - constants.ConditionalOrder_Alice_Num1_Id1_Clob0_Sell50_Price5_GTB30_TakeProfit20.OrderId: false, - }, - expectedProcessProposerMatchesEvents: types.ProcessProposerMatchesEvents{ - PlacedStatefulCancellationOrderIds: []types.OrderId{ - constants.ConditionalOrder_Alice_Num1_Id1_Clob0_Sell50_Price5_GTB30_TakeProfit20.OrderId, - }, - ExpiredStatefulOrderIds: []types.OrderId{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_StopLoss20.OrderId, - constants.ConditionalOrder_Alice_Num0_Id0_Clob1_Buy5_Price10_GTBT15_TakeProfit20.OrderId, - constants.ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price25_GTBT15_StopLoss25.OrderId, - }, - BlockHeight: blockHeight, - }, - }, - `Adds newly-placed conditional order to UntriggeredConditionalOrders, but does not add - cancelled order`: { - blockTime: unixTimeTen, - setupState: func(ctx sdk.Context, ks keepertest.ClobKeepersTestContext, m *mocks.MemClob) { - orders := []types.Order{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_TakeProfit10, - constants.ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price10_GTBT15_TakeProfit5, - constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Buy20_Price10_GTBT15_TakeProfit10, - } - for _, order := range orders { - ks.ClobKeeper.SetLongTermOrderPlacement(ctx, order, blockHeight) - } - - ks.ClobKeeper.MustSetProcessProposerMatchesEvents( - ctx, - types.ProcessProposerMatchesEvents{ - PlacedConditionalOrderIds: []types.OrderId{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_TakeProfit10.OrderId, - constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Buy20_Price10_GTBT15_TakeProfit10.OrderId, - }, - PlacedStatefulCancellationOrderIds: []types.OrderId{ - constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Buy20_Price10_GTBT15_TakeProfit10.OrderId, - }, - BlockHeight: blockHeight, - }, - ) - }, - expectedUntriggeredConditionalOrders: map[types.ClobPairId]*keeper.UntriggeredConditionalOrders{ - constants.ClobPair_Btc.GetClobPairId(): { - OrdersToTriggerWhenOraclePriceLTETriggerPrice: []types.Order{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_TakeProfit10, - }, - OrdersToTriggerWhenOraclePriceGTETriggerPrice: []types.Order{}, - }, - }, - expectedProcessProposerMatchesEvents: types.ProcessProposerMatchesEvents{ - PlacedConditionalOrderIds: []types.OrderId{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_TakeProfit10.OrderId, - constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Buy20_Price10_GTBT15_TakeProfit10.OrderId, - }, - PlacedStatefulCancellationOrderIds: []types.OrderId{ - constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Buy20_Price10_GTBT15_TakeProfit10.OrderId, - }, - BlockHeight: blockHeight, - }, - }, `Polls triggered conditional orders from UntriggeredConditionalOrders, update state and ProcessProposerMatchesEvents`: { blockTime: unixTimeTen, @@ -366,42 +173,25 @@ func TestEndBlocker_Success(t *testing.T) { }) require.NoError(t, err) - ks.ClobKeeper.UntriggeredConditionalOrders = map[types.ClobPairId]*keeper.UntriggeredConditionalOrders{ - constants.ClobPair_Btc.GetClobPairId(): { - // 10 oracle price subticks triggers 3 orders here. - OrdersToTriggerWhenOraclePriceLTETriggerPrice: []types.Order{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_TakeProfit10, - constants.ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price10_GTBT15_TakeProfit5, - constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Buy20_Price10_GTBT15_TakeProfit10, - constants.ConditionalOrder_Alice_Num0_Id3_Clob0_Sell25_Price10_GTBT15_StopLoss10, - }, - // 10 oracle price subticks triggers no orders here. - OrdersToTriggerWhenOraclePriceGTETriggerPrice: []types.Order{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price20_GTBT15_StopLoss20, - constants.ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price25_GTBT15_StopLoss25, - constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Sell20_Price20_GTBT15_TakeProfit20, - constants.ConditionalOrder_Alice_Num0_Id3_Clob0_Buy25_Price25_GTBT15_StopLoss25, - }, - }, - constants.ClobPair_Eth.GetClobPairId(): { - // 35 oracle price subticks triggers no orders here. - OrdersToTriggerWhenOraclePriceLTETriggerPrice: []types.Order{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob1_Buy5_Price10_GTBT15_TakeProfit30, - }, - // 35 oracle price subticks triggers one order here. - OrdersToTriggerWhenOraclePriceGTETriggerPrice: []types.Order{ - constants.ConditionalOrder_Alice_Num0_Id3_Clob1_Buy25_Price10_GTBT15_StopLoss20, - }, - }, + untrigCondOrders := []types.Order{ + // 10 oracle price subticks triggers 3 orders here. + constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_TakeProfit10, + constants.ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price10_GTBT15_TakeProfit5, + constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Buy20_Price10_GTBT15_TakeProfit10, + constants.ConditionalOrder_Alice_Num0_Id3_Clob0_Sell25_Price10_GTBT15_StopLoss10, + // 10 oracle price subticks triggers no orders here. + constants.ConditionalOrder_Alice_Num1_Id0_Clob0_Buy5_Price20_GTBT15_StopLoss20, + constants.ConditionalOrder_Alice_Num1_Id1_Clob0_Buy15_Price25_GTBT15_StopLoss25, + constants.ConditionalOrder_Alice_Num1_Id2_Clob0_Sell20_Price20_GTBT15_TakeProfit20, + constants.ConditionalOrder_Alice_Num1_Id3_Clob0_Buy25_Price25_GTBT15_StopLoss25, + // 35 oracle price subticks triggers no orders here. + constants.ConditionalOrder_Alice_Num0_Id0_Clob1_Buy5_Price10_GTBT15_TakeProfit30, + // 35 oracle price subticks triggers one order here. + constants.ConditionalOrder_Alice_Num0_Id3_Clob1_Buy25_Price10_GTBT15_StopLoss20, } - for _, untrigCondOrders := range ks.ClobKeeper.UntriggeredConditionalOrders { - for _, conditionalOrder := range untrigCondOrders.OrdersToTriggerWhenOraclePriceGTETriggerPrice { - ks.ClobKeeper.SetLongTermOrderPlacement(ctx, conditionalOrder, blockHeight) - } - for _, conditionalOrder := range untrigCondOrders.OrdersToTriggerWhenOraclePriceLTETriggerPrice { - ks.ClobKeeper.SetLongTermOrderPlacement(ctx, conditionalOrder, blockHeight) - } + for _, order := range untrigCondOrders { + ks.ClobKeeper.SetLongTermOrderPlacement(ctx, order, blockHeight) } ks.ClobKeeper.MustSetProcessProposerMatchesEvents( @@ -433,10 +223,10 @@ func TestEndBlocker_Success(t *testing.T) { constants.ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price10_GTBT15_TakeProfit5, }, OrdersToTriggerWhenOraclePriceGTETriggerPrice: []types.Order{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price20_GTBT15_StopLoss20, - constants.ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price25_GTBT15_StopLoss25, - constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Sell20_Price20_GTBT15_TakeProfit20, - constants.ConditionalOrder_Alice_Num0_Id3_Clob0_Buy25_Price25_GTBT15_StopLoss25, + constants.ConditionalOrder_Alice_Num1_Id0_Clob0_Buy5_Price20_GTBT15_StopLoss20, + constants.ConditionalOrder_Alice_Num1_Id1_Clob0_Buy15_Price25_GTBT15_StopLoss25, + constants.ConditionalOrder_Alice_Num1_Id2_Clob0_Sell20_Price20_GTBT15_TakeProfit20, + constants.ConditionalOrder_Alice_Num1_Id3_Clob0_Buy25_Price25_GTBT15_StopLoss25, }, }, constants.ClobPair_Eth.GetClobPairId(): { @@ -816,10 +606,16 @@ func TestEndBlocker_Success(t *testing.T) { } if tc.expectedUntriggeredConditionalOrders != nil { + // Get untriggered orders from state and convert into + // `map[types.ClobPairId]*keeper.UntriggeredConditionalOrders`. + gotUntriggered := keeper.OrganizeUntriggeredConditionalOrdersFromState( + ks.ClobKeeper.GetAllUntriggeredConditionalOrders(ctx), + ) + require.Equal( t, tc.expectedUntriggeredConditionalOrders, - ks.ClobKeeper.UntriggeredConditionalOrders, + gotUntriggered, ) } diff --git a/protocol/x/clob/keeper/clob_pair_test.go b/protocol/x/clob/keeper/clob_pair_test.go index 4ae6fb3e14..9efa5cd314 100644 --- a/protocol/x/clob/keeper/clob_pair_test.go +++ b/protocol/x/clob/keeper/clob_pair_test.go @@ -21,7 +21,6 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/testutil/nullify" perptest "github.com/dydxprotocol/v4-chain/protocol/testutil/perpetuals" pricestest "github.com/dydxprotocol/v4-chain/protocol/testutil/prices" - "github.com/dydxprotocol/v4-chain/protocol/x/clob/keeper" "github.com/dydxprotocol/v4-chain/protocol/x/clob/memclob" "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals" @@ -684,10 +683,6 @@ func TestUpdateClobPair_FinalSettlement(t *testing.T) { } } - ks.ClobKeeper.UntriggeredConditionalOrders = map[types.ClobPairId]*keeper.UntriggeredConditionalOrders{ - 0: {}, // leaving blank, orders here don't matter in particular since we clear the whole key - } - clobPair.Status = types.ClobPair_STATUS_FINAL_SETTLEMENT err = ks.ClobKeeper.UpdateClobPair(ks.Ctx, clobPair) require.NoError(t, err) @@ -711,10 +706,6 @@ func TestUpdateClobPair_FinalSettlement(t *testing.T) { }, ppme.RemovedStatefulOrderIds, ) - - // Verify UntriggeredConditionalOrders is cleared. - _, found := ks.ClobKeeper.UntriggeredConditionalOrders[0] - require.False(t, found) } func TestUpdateClobPair(t *testing.T) { diff --git a/protocol/x/clob/keeper/final_settlement.go b/protocol/x/clob/keeper/final_settlement.go index a553985cac..48f4c23e29 100644 --- a/protocol/x/clob/keeper/final_settlement.go +++ b/protocol/x/clob/keeper/final_settlement.go @@ -14,9 +14,6 @@ import ( func (k Keeper) mustTransitionToFinalSettlement(ctx sdk.Context, clobPairId types.ClobPairId) { // Forcefully cancel all stateful orders from state for this clob pair. k.mustCancelStatefulOrdersForFinalSettlement(ctx, clobPairId) - - // Delete untriggered conditional orders for this clob pair from memory. - delete(k.UntriggeredConditionalOrders, clobPairId) } // mustCancelStatefulOrdersForFinalSettlement forcefully cancels all stateful orders diff --git a/protocol/x/clob/keeper/keeper.go b/protocol/x/clob/keeper/keeper.go index 4ea1edee3a..5ab67186e1 100644 --- a/protocol/x/clob/keeper/keeper.go +++ b/protocol/x/clob/keeper/keeper.go @@ -28,9 +28,8 @@ type ( transientStoreKey storetypes.StoreKey authorities map[string]struct{} - MemClob types.MemClob - UntriggeredConditionalOrders map[types.ClobPairId]*UntriggeredConditionalOrders - PerpetualIdToClobPairId map[uint32][]types.ClobPairId + MemClob types.MemClob + PerpetualIdToClobPairId map[uint32][]types.ClobPairId subaccountsKeeper types.SubaccountsKeeper assetsKeeper types.AssetsKeeper @@ -92,28 +91,27 @@ func NewKeeper( daemonLiquidationInfo *liquidationtypes.DaemonLiquidationInfo, ) *Keeper { keeper := &Keeper{ - cdc: cdc, - storeKey: storeKey, - memKey: memKey, - transientStoreKey: liquidationsStoreKey, - authorities: lib.UniqueSliceToSet(authorities), - MemClob: memClob, - UntriggeredConditionalOrders: make(map[types.ClobPairId]*UntriggeredConditionalOrders), - PerpetualIdToClobPairId: make(map[uint32][]types.ClobPairId), - subaccountsKeeper: subaccountsKeeper, - assetsKeeper: assetsKeeper, - blockTimeKeeper: blockTimeKeeper, - bankKeeper: bankKeeper, - feeTiersKeeper: feeTiersKeeper, - perpetualsKeeper: perpetualsKeeper, - pricesKeeper: pricesKeeper, - statsKeeper: statsKeeper, - rewardsKeeper: rewardsKeeper, - indexerEventManager: indexerEventManager, - streamingManager: grpcStreamingManager, - memStoreInitialized: &atomic.Bool{}, // False by default. - initialized: &atomic.Bool{}, // False by default. - txDecoder: txDecoder, + cdc: cdc, + storeKey: storeKey, + memKey: memKey, + transientStoreKey: liquidationsStoreKey, + authorities: lib.UniqueSliceToSet(authorities), + MemClob: memClob, + PerpetualIdToClobPairId: make(map[uint32][]types.ClobPairId), + subaccountsKeeper: subaccountsKeeper, + assetsKeeper: assetsKeeper, + blockTimeKeeper: blockTimeKeeper, + bankKeeper: bankKeeper, + feeTiersKeeper: feeTiersKeeper, + perpetualsKeeper: perpetualsKeeper, + pricesKeeper: pricesKeeper, + statsKeeper: statsKeeper, + rewardsKeeper: rewardsKeeper, + indexerEventManager: indexerEventManager, + streamingManager: grpcStreamingManager, + memStoreInitialized: &atomic.Bool{}, // False by default. + initialized: &atomic.Bool{}, // False by default. + txDecoder: txDecoder, mevTelemetryConfig: MevTelemetryConfig{ Enabled: clobFlags.MevTelemetryEnabled, Hosts: clobFlags.MevTelemetryHosts, @@ -188,9 +186,6 @@ func (k Keeper) Initialize(ctx sdk.Context) { // Initialize the untriggered conditional orders data structure with untriggered // conditional orders in state. k.HydrateClobPairAndPerpetualMapping(checkCtx) - // Initialize the untriggered conditional orders data structure with untriggered - // conditional orders in state. - k.HydrateUntriggeredConditionalOrders(checkCtx) } // InitMemStore initializes the memstore of the `clob` keeper. diff --git a/protocol/x/clob/keeper/orders.go b/protocol/x/clob/keeper/orders.go index 9986580930..bb5a2c4ddd 100644 --- a/protocol/x/clob/keeper/orders.go +++ b/protocol/x/clob/keeper/orders.go @@ -1243,38 +1243,6 @@ func (k Keeper) InitStatefulOrders( } } -// HydrateUntriggeredConditionalOrders inserts all untriggered conditional orders in state into the -// `UntriggeredConditionalOrders` data structure. Note that all untriggered conditional orders will -// be ordered by time priority. This function should only be called on application startup. -func (k Keeper) HydrateUntriggeredConditionalOrders( - ctx sdk.Context, -) { - defer telemetry.ModuleMeasureSince( - types.ModuleName, - time.Now(), - metrics.ConditionalOrderUntriggered, - metrics.Hydrate, - metrics.Latency, - ) - - // Get all untriggered conditional orders in state, ordered by time priority ascending order, - // and add them to the `UntriggeredConditionalOrders` data structure. - untriggeredConditionalOrders := k.GetAllUntriggeredConditionalOrders(ctx) - k.AddUntriggeredConditionalOrders( - ctx, - lib.MapSlice( - untriggeredConditionalOrders, - func(o types.Order) types.OrderId { - return o.OrderId - }, - ), - // Note both of these arguments are empty slices since the untriggered conditional orders - // shouldn't be expired or canceled. - map[types.OrderId]struct{}{}, - map[types.OrderId]struct{}{}, - ) -} - // sendOffchainMessagesWithTxHash sends all the `Message` in the offchainUpdates passed in along with // an additional header for the transaction hash passed in. func (k Keeper) sendOffchainMessagesWithTxHash( diff --git a/protocol/x/clob/keeper/orders_test.go b/protocol/x/clob/keeper/orders_test.go index 3eb6427e22..0d98b99ebb 100644 --- a/protocol/x/clob/keeper/orders_test.go +++ b/protocol/x/clob/keeper/orders_test.go @@ -2048,125 +2048,6 @@ func TestInitStatefulOrders(t *testing.T) { } } -func TestHydrateUntriggeredConditionalOrdersInMemClob(t *testing.T) { - tests := map[string]struct { - // CLOB module state. - statefulOrdersInState []types.Order - isConditionalOrderTriggered map[types.OrderId]bool - }{ - `Can initialize untriggered conditional orders with 0 stateful orders in state`: { - statefulOrdersInState: []types.Order{}, - isConditionalOrderTriggered: map[types.OrderId]bool{}, - }, - `Can initialize untriggered conditional orders with both Long-Term and triggered - conditional orders in state`: { - statefulOrdersInState: []types.Order{ - constants.ConditionalOrder_Bob_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10_TP_50005, - constants.LongTermOrder_Alice_Num0_Id0_Clob0_Buy100_Price10_GTBT15_PO, - }, - isConditionalOrderTriggered: map[types.OrderId]bool{ - constants.ConditionalOrder_Bob_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10_TP_50005. - OrderId: true, - }, - }, - `Can initialize untriggered conditional orders with both Long-Term, untriggered conditional - orders, and triggered conditional orders in state`: { - statefulOrdersInState: []types.Order{ - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10, - constants.ConditionalOrder_Bob_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10_TP_50005, - constants.LongTermOrder_Alice_Num0_Id0_Clob0_Buy100_Price10_GTBT15_PO, - constants.ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price10_GTBT15_TakeProfit20, - constants.LongTermOrder_Bob_Num0_Id0_Clob0_Buy25_Price30_GTBT10, - }, - isConditionalOrderTriggered: map[types.OrderId]bool{ - constants.ConditionalOrder_Bob_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10_TP_50005. - OrderId: true, - }, - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // Setup state. - memClob := &mocks.MemClob{} - memClob.On("SetClobKeeper", mock.Anything).Return() - - indexerEventManager := &mocks.IndexerEventManager{} - - ks := keepertest.NewClobKeepersTestContext(t, memClob, &mocks.BankKeeper{}, indexerEventManager) - prices.InitGenesis(ks.Ctx, *ks.PricesKeeper, constants.Prices_DefaultGenesisState) - perpetuals.InitGenesis(ks.Ctx, *ks.PerpetualsKeeper, constants.Perpetuals_DefaultGenesisState) - - // Create CLOB pair. - memClob.On("CreateOrderbook", mock.Anything, constants.ClobPair_Btc).Return() - indexerEventManager.On("AddTxnEvent", - ks.Ctx, - indexerevents.SubtypePerpetualMarket, - indexerevents.PerpetualMarketEventVersion, - indexer_manager.GetBytes( - indexerevents.NewPerpetualMarketCreateEvent( - 0, - 0, - constants.Perpetuals_DefaultGenesisState.Perpetuals[0].Params.Ticker, - constants.Perpetuals_DefaultGenesisState.Perpetuals[0].Params.MarketId, - constants.ClobPair_Btc.Status, - constants.ClobPair_Btc.QuantumConversionExponent, - constants.Perpetuals_DefaultGenesisState.Perpetuals[0].Params.AtomicResolution, - constants.ClobPair_Btc.SubticksPerTick, - constants.ClobPair_Btc.StepBaseQuantums, - constants.Perpetuals_DefaultGenesisState.Perpetuals[0].Params.LiquidityTier, - constants.Perpetuals_DefaultGenesisState.Perpetuals[0].Params.MarketType, - ), - ), - ).Once().Return() - _, err := ks.ClobKeeper.CreatePerpetualClobPair( - ks.Ctx, - constants.ClobPair_Btc.Id, - clobtest.MustPerpetualId(constants.ClobPair_Btc), - satypes.BaseQuantums(constants.ClobPair_Btc.StepBaseQuantums), - constants.ClobPair_Btc.QuantumConversionExponent, - constants.ClobPair_Btc.SubticksPerTick, - constants.ClobPair_Btc.Status, - ) - require.NoError(t, err) - - // Create each stateful order placement in state. - expectedUntriggeredConditionalOrders := make(map[types.ClobPairId]*keeper.UntriggeredConditionalOrders) - for i, order := range tc.statefulOrdersInState { - require.True(t, order.IsStatefulOrder()) - - // Write the stateful order placement to state. - ks.ClobKeeper.SetLongTermOrderPlacement(ks.Ctx, order, uint32(i)) - - // No further state updates are required if this isn't a conditional order. - if !order.IsConditionalOrder() { - continue - } - - // If it's a triggered conditional order, ensure it's triggered in state and skip - // updating the expected untriggered conditional orders. - if tc.isConditionalOrderTriggered[order.OrderId] { - ks.ClobKeeper.MustTriggerConditionalOrder(ks.Ctx, order.OrderId) - continue - } - - // This is an untriggered conditional order and we expect it to be returned. - untriggeredConditionalOrders, exists := expectedUntriggeredConditionalOrders[order.GetClobPairId()] - if !exists { - untriggeredConditionalOrders = ks.ClobKeeper.NewUntriggeredConditionalOrders() - expectedUntriggeredConditionalOrders[order.GetClobPairId()] = untriggeredConditionalOrders - } - untriggeredConditionalOrders.AddUntriggeredConditionalOrder(order) - } - - // Run the test and verify expectations. - ks.ClobKeeper.HydrateUntriggeredConditionalOrders(ks.Ctx) - - require.Equal(t, expectedUntriggeredConditionalOrders, ks.ClobKeeper.UntriggeredConditionalOrders) - }) - } -} - func TestPlaceStatefulOrdersFromLastBlock(t *testing.T) { tests := map[string]struct { orders []types.Order diff --git a/protocol/x/clob/keeper/untriggered_conditional_orders.go b/protocol/x/clob/keeper/untriggered_conditional_orders.go index 8ff6e3a52d..ec3a0de07d 100644 --- a/protocol/x/clob/keeper/untriggered_conditional_orders.go +++ b/protocol/x/clob/keeper/untriggered_conditional_orders.go @@ -51,43 +51,6 @@ func (untriggeredOrders *UntriggeredConditionalOrders) IsEmpty() bool { len(untriggeredOrders.OrdersToTriggerWhenOraclePriceGTETriggerPrice) == 0 } -// AddUntriggeredConditionalOrders takes in a list of newly-placed conditional order ids and adds them -// to the in-memory UntriggeredConditionalOrders struct, filtering out orders that have been cancelled -// or expired in the last block. This function is used in EndBlocker and on application startup. -func (k Keeper) AddUntriggeredConditionalOrders( - ctx sdk.Context, - placedConditionalOrderIds []types.OrderId, - placedStatefulCancellationOrderIds map[types.OrderId]struct{}, - expiredStatefulOrderIdsSet map[types.OrderId]struct{}, -) { - for _, orderId := range placedConditionalOrderIds { - _, isCancelled := placedStatefulCancellationOrderIds[orderId] - _, isExpired := expiredStatefulOrderIdsSet[orderId] - if isCancelled || isExpired { - continue - } - - orderPlacement, exists := k.GetUntriggeredConditionalOrderPlacement(ctx, orderId) - if !exists { - panic( - fmt.Sprintf( - "AddUntriggeredConditionalOrders: order placement does not exist in state for untriggered "+ - "conditional order id, OrderId %+v.", - orderId, - ), - ) - } - - clobPairId := types.ClobPairId(orderId.GetClobPairId()) - untriggeredConditionalOrders, exists := k.UntriggeredConditionalOrders[clobPairId] - if !exists { - untriggeredConditionalOrders = k.NewUntriggeredConditionalOrders() - k.UntriggeredConditionalOrders[clobPairId] = untriggeredConditionalOrders - } - untriggeredConditionalOrders.AddUntriggeredConditionalOrder(orderPlacement.GetOrder()) - } -} - // AddUntriggeredConditionalOrder adds an untriggered conditional order to the UntriggeredConditionalOrders // data structure. It will panic if the order is not a conditional order. func (untriggeredOrders *UntriggeredConditionalOrders) AddUntriggeredConditionalOrder(order types.Order) { @@ -122,58 +85,6 @@ func (untriggeredOrders *UntriggeredConditionalOrders) AddUntriggeredConditional } } -// PruneUntriggeredConditionalOrders takes in lists of expired and cancelled stateful order ids and removes -// all respective orders from the in-memory `UntriggeredConditionalOrders` data structure. This data structure -// stores untriggered orders in a map of ClobPairId -> []Order, so we first group orders by ClobPairId and then -// call `UntriggeredConditionalOrders.RemoveExpiredUntriggeredConditionalOrders` on each ClobPairId. -func (k Keeper) PruneUntriggeredConditionalOrders( - expiredStatefulOrderIds []types.OrderId, - cancelledStatefulOrderIds []types.OrderId, -) { - // Merge lists of order ids. - orderIdsToPrune := lib.UniqueSliceToSet(expiredStatefulOrderIds) - for _, orderId := range cancelledStatefulOrderIds { - if _, exists := orderIdsToPrune[orderId]; exists { - panic( - fmt.Sprintf( - "PruneUntriggeredConditionalOrders: duplicate order id %+v in expired and "+ - "cancelled order lists", orderId, - ), - ) - } - orderIdsToPrune[orderId] = struct{}{} - } - - prunableUntriggeredConditionalOrderIdsByClobPair := make(map[types.ClobPairId][]types.OrderId) - for orderId := range orderIdsToPrune { - // If the order id is conditional, add to prunable list of untriggered order ids. - // Triggered conditional orders will be effectively ignored during removal as they are not part of - // UntriggeredConditionalOrders anymore. No need to filter out here, we can avoid memstore reads. - if orderId.IsConditionalOrder() { - clobPairId := types.ClobPairId(orderId.GetClobPairId()) - if _, exists := prunableUntriggeredConditionalOrderIdsByClobPair[clobPairId]; !exists { - prunableUntriggeredConditionalOrderIdsByClobPair[clobPairId] = []types.OrderId{} - } - - prunableUntriggeredConditionalOrderIdsByClobPair[clobPairId] = append( - prunableUntriggeredConditionalOrderIdsByClobPair[clobPairId], - orderId, - ) - } - } - - for clobPairId := range prunableUntriggeredConditionalOrderIdsByClobPair { - if untriggeredConditionalOrders, exists := k.UntriggeredConditionalOrders[clobPairId]; exists { - untriggeredConditionalOrders.RemoveUntriggeredConditionalOrders( - prunableUntriggeredConditionalOrderIdsByClobPair[clobPairId], - ) - if untriggeredConditionalOrders.IsEmpty() { - delete(k.UntriggeredConditionalOrders, clobPairId) - } - } - } -} - // RemoveUntriggeredConditionalOrders removes a list of order ids from the `UntriggeredConditionalOrders` // data structure. This function will panic if the order ids contained involve more than one ClobPairId. func (untriggeredOrders *UntriggeredConditionalOrders) RemoveUntriggeredConditionalOrders( @@ -263,6 +174,26 @@ func (untriggeredOrders *UntriggeredConditionalOrders) PollTriggeredConditionalO return triggeredOrderIds } +// OrganizeUntriggeredConditionalOrdersFromState takes in a list of conditional orders read from +// state, organize them and return in form of `UntriggeredConditionalOrders` struct. +func OrganizeUntriggeredConditionalOrdersFromState( + conditonalOrdersFromState []types.Order, +) map[types.ClobPairId]*UntriggeredConditionalOrders { + ret := make(map[types.ClobPairId]*UntriggeredConditionalOrders) + + for _, order := range conditonalOrdersFromState { + clobPairId := types.ClobPairId(order.GetClobPairId()) + untriggeredConditionalOrders, exists := ret[clobPairId] + if !exists { + untriggeredConditionalOrders = NewUntriggeredConditionalOrders() + ret[clobPairId] = untriggeredConditionalOrders + } + untriggeredConditionalOrders.AddUntriggeredConditionalOrder(order) + } + + return ret +} + // MaybeTriggerConditionalOrders queries the prices module for price updates and triggers // any conditional orders in `UntriggeredConditionalOrders` that can be triggered. For each triggered // order, it takes the stateful order placement stored in Untriggered state and moves it to Triggered state. @@ -277,15 +208,19 @@ func (k Keeper) MaybeTriggerConditionalOrders(ctx sdk.Context) (allTriggeredOrde time.Now(), ) + clobPairToUntriggeredConditionals := OrganizeUntriggeredConditionalOrdersFromState( + k.GetAllUntriggeredConditionalOrders(ctx), + ) + // Sort the keys for the untriggered conditional orders struct. We need to trigger // the conditional orders in an ordered way to have deterministic state writes. - sortedKeys := lib.GetSortedKeys[types.SortedClobPairId](k.UntriggeredConditionalOrders) + sortedKeys := lib.GetSortedKeys[types.SortedClobPairId](clobPairToUntriggeredConditionals) allTriggeredOrderIds = make([]types.OrderId, 0) // For all clob pair ids in UntriggeredConditionalOrders, fetch the updated // oracle price and poll out triggered conditional orders. for _, clobPairId := range sortedKeys { - untriggered := k.UntriggeredConditionalOrders[clobPairId] + untriggered := clobPairToUntriggeredConditionals[clobPairId] clobPair, found := k.GetClobPair(ctx, clobPairId) if !found { panic( @@ -319,9 +254,6 @@ func (k Keeper) MaybeTriggerConditionalOrders(ctx sdk.Context) (allTriggeredOrde allTriggeredOrderIds = append(allTriggeredOrderIds, triggered...) } - // Set the modified untriggeredConditionalOrders back on the keeper field. - k.UntriggeredConditionalOrders[clobPairId] = untriggered - // Gauge the number of untriggered orders. metrics.SetGaugeWithLabels( metrics.ClobNumUntriggeredOrders, diff --git a/protocol/x/clob/keeper/untriggered_conditional_orders_test.go b/protocol/x/clob/keeper/untriggered_conditional_orders_test.go index db84fe6f04..bfb85b41e6 100644 --- a/protocol/x/clob/keeper/untriggered_conditional_orders_test.go +++ b/protocol/x/clob/keeper/untriggered_conditional_orders_test.go @@ -2,7 +2,6 @@ package keeper_test import ( "fmt" - testApp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" "math/big" "testing" @@ -88,10 +87,7 @@ func TestAddUntriggeredConditionalOrder(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - tApp := testApp.NewTestAppBuilder(t).Build() - tApp.InitChain() - untriggeredConditionalOrders := tApp.App.ClobKeeper.NewUntriggeredConditionalOrders() - tApp.App.ClobKeeper.UntriggeredConditionalOrders[0] = untriggeredConditionalOrders + untriggeredConditionalOrders := keeper.NewUntriggeredConditionalOrders() for _, order := range tc.conditionalOrdersToAdd { untriggeredConditionalOrders.AddUntriggeredConditionalOrder(order) @@ -127,6 +123,98 @@ func TestAddUntriggeredConditionalOrder_NonConditionalOrder(t *testing.T) { ) } +func TestOrganizeUntriggeredConditionalOrdersFromState(t *testing.T) { + tests := map[string]struct { + // Setup. + conditionalOrdersFromState []types.Order + + // Expectations. + expectedUntriggeredConditionalOrders map[types.ClobPairId]*keeper.UntriggeredConditionalOrders + }{ + "Only GTE orders, one ClobPair": { + conditionalOrdersFromState: []types.Order{ + // GTE orders + constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_StopLoss20, + constants.ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price10_GTBT15_StopLoss20, + constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Buy20_Price10_GTBT15_StopLoss20, + }, + expectedUntriggeredConditionalOrders: map[types.ClobPairId]*keeper.UntriggeredConditionalOrders{ + 0: { + OrdersToTriggerWhenOraclePriceLTETriggerPrice: []types.Order{}, + OrdersToTriggerWhenOraclePriceGTETriggerPrice: []types.Order{ + constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_StopLoss20, + constants.ConditionalOrder_Alice_Num0_Id1_Clob0_Buy15_Price10_GTBT15_StopLoss20, + constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Buy20_Price10_GTBT15_StopLoss20, + }, + }, + }, + }, + "Both GTE and LTE orders, one ClobPair": { + conditionalOrdersFromState: []types.Order{ + // GTE + constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_StopLoss20, + // LTE + constants.ConditionalOrder_Alice_Num0_Id3_Clob0_Buy25_Price10_GTBT15_TakeProfit20, + }, + expectedUntriggeredConditionalOrders: map[types.ClobPairId]*keeper.UntriggeredConditionalOrders{ + 0: { + OrdersToTriggerWhenOraclePriceLTETriggerPrice: []types.Order{ + constants.ConditionalOrder_Alice_Num0_Id3_Clob0_Buy25_Price10_GTBT15_TakeProfit20, + }, + OrdersToTriggerWhenOraclePriceGTETriggerPrice: []types.Order{ + constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_StopLoss20, + }, + }, + }, + }, + "Multiple ClobPair + both LTE and GTE orders": { + conditionalOrdersFromState: []types.Order{ + // GTE, ClobPair 1 + constants.ConditionalOrder_Alice_Num0_Id0_Clob1_Buy5_Price10_GTBT15_TakeProfit20, + // GTE, ClobPair 0 + constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_StopLoss20, + // LTE, ClobPair 0 + constants.ConditionalOrder_Alice_Num0_Id3_Clob0_Buy25_Price10_GTBT15_TakeProfit20, + // GTE, ClobPair 0 + constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Sell20_Price20_GTBT15_TakeProfit20, + // LTE, ClobPair 1 + constants.ConditionalOrder_Alice_Num0_Id1_Clob1_Buy5_Price10_GTBT15_StopLoss20, + }, + expectedUntriggeredConditionalOrders: map[types.ClobPairId]*keeper.UntriggeredConditionalOrders{ + 0: { + OrdersToTriggerWhenOraclePriceLTETriggerPrice: []types.Order{ + constants.ConditionalOrder_Alice_Num0_Id3_Clob0_Buy25_Price10_GTBT15_TakeProfit20, + }, + OrdersToTriggerWhenOraclePriceGTETriggerPrice: []types.Order{ + constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15_StopLoss20, + constants.ConditionalOrder_Alice_Num0_Id2_Clob0_Sell20_Price20_GTBT15_TakeProfit20, + }, + }, + 1: { + OrdersToTriggerWhenOraclePriceLTETriggerPrice: []types.Order{ + constants.ConditionalOrder_Alice_Num0_Id0_Clob1_Buy5_Price10_GTBT15_TakeProfit20, + }, + OrdersToTriggerWhenOraclePriceGTETriggerPrice: []types.Order{ + constants.ConditionalOrder_Alice_Num0_Id1_Clob1_Buy5_Price10_GTBT15_StopLoss20, + }, + }, + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got := keeper.OrganizeUntriggeredConditionalOrdersFromState(tc.conditionalOrdersFromState) + + require.Equal( + t, + tc.expectedUntriggeredConditionalOrders, + got, + ) + }) + } +} + func TestRemoveUntriggeredConditionalOrders(t *testing.T) { tests := map[string]struct { // Setup. @@ -194,10 +282,7 @@ func TestRemoveUntriggeredConditionalOrders(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - tApp := testApp.NewTestAppBuilder(t).Build() - tApp.InitChain() - untriggeredConditionalOrders := tApp.App.ClobKeeper.NewUntriggeredConditionalOrders() - tApp.App.ClobKeeper.UntriggeredConditionalOrders[0] = untriggeredConditionalOrders + untriggeredConditionalOrders := keeper.NewUntriggeredConditionalOrders() for _, order := range tc.conditionalOrdersToAdd { untriggeredConditionalOrders.AddUntriggeredConditionalOrder(order)