From 71529cefb71812ee00a3b4120d9e9e827643f14b Mon Sep 17 00:00:00 2001 From: Jakob Herlitz <125316911+jakob-dydx@users.noreply.github.com> Date: Wed, 25 Oct 2023 10:55:38 -0700 Subject: [PATCH] [CLOB-184] - e2e short term order replacement tests (#491) * initial test * add e2e tests for short term replacements * remove misleading comment * add IOC/FOK replacement tests * format * add test for replacing short term order with order on opposite side after partial fill * fix IOC test that used FOK order --- protocol/x/clob/e2e/app_test.go | 33 ++ protocol/x/clob/e2e/short_term_orders_test.go | 415 ++++++++++++++++++ .../clob/types/message_proposed_operations.go | 2 - 3 files changed, 448 insertions(+), 2 deletions(-) diff --git a/protocol/x/clob/e2e/app_test.go b/protocol/x/clob/e2e/app_test.go index d2de9e50b4..0f6794f3e0 100644 --- a/protocol/x/clob/e2e/app_test.go +++ b/protocol/x/clob/e2e/app_test.go @@ -83,6 +83,39 @@ var ( }, testapp.DefaultGenesis(), )) + // replacement of above order with smaller quantums + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21 = *clobtypes.NewMsgPlaceOrder(MustScaleOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Alice_Num0, ClientId: 0, ClobPairId: 0}, + Side: clobtypes.Order_SIDE_BUY, + Quantums: 5, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 21}, + }, + testapp.DefaultGenesis(), + )) + // replacement of order with larger quantums + PlaceOrder_Alice_Num0_Id0_Clob0_Buy7_Price10_GTB21 = *clobtypes.NewMsgPlaceOrder(MustScaleOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Alice_Num0, ClientId: 0, ClobPairId: 0}, + Side: clobtypes.Order_SIDE_BUY, + Quantums: 7, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 21}, + }, + testapp.DefaultGenesis(), + )) + // replacement of order on opposite side + PlaceOrder_Alice_Num0_Id0_Clob0_Sell6_Price10_GTB21 = *clobtypes.NewMsgPlaceOrder(MustScaleOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Alice_Num0, ClientId: 0, ClobPairId: 0}, + Side: clobtypes.Order_SIDE_SELL, + Quantums: 6, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 21}, + }, + testapp.DefaultGenesis(), + )) PlaceOrder_Alice_Num0_Id0_Clob1_Buy5_Price10_GTB20 = *clobtypes.NewMsgPlaceOrder(MustScaleOrder( clobtypes.Order{ OrderId: clobtypes.OrderId{SubaccountId: constants.Alice_Num0, ClientId: 0, ClobPairId: 1}, diff --git a/protocol/x/clob/e2e/short_term_orders_test.go b/protocol/x/clob/e2e/short_term_orders_test.go index d5b473454d..8147eaab9d 100644 --- a/protocol/x/clob/e2e/short_term_orders_test.go +++ b/protocol/x/clob/e2e/short_term_orders_test.go @@ -605,3 +605,418 @@ func TestPlaceOrder(t *testing.T) { }) } } + +func TestShortTermOrderReplacements(t *testing.T) { + order := PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20 + fok_replacement := order + fok_replacement.Order.GoodTilOneof = &clobtypes.Order_GoodTilBlock{GoodTilBlock: 21} + fok_replacement.Order.TimeInForce = clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL + ioc_replacement := fok_replacement + ioc_replacement.Order.TimeInForce = clobtypes.Order_TIME_IN_FORCE_IOC + + type orderIdExpectations struct { + shouldExistOnMemclob bool + expectedOrder clobtypes.Order + expectedFillAmount uint64 + } + type blockOrdersAndExpectations struct { + ordersToPlace []clobtypes.MsgPlaceOrder + orderIdsExpectations map[clobtypes.OrderId]orderIdExpectations + } + tests := map[string]struct { + blocks []blockOrdersAndExpectations + }{ + "Success: Replace in same block on same side": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21, + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21.Order, + }, + }, + }, + }, + }, + "Success: Replace in same block on opposite side": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + PlaceOrder_Alice_Num0_Id0_Clob0_Sell6_Price10_GTB21, + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Sell6_Price10_GTB21.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Sell6_Price10_GTB21.Order, + }, + }, + }, + }, + }, + "Success: Replace in next block": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order, + }, + }, + }, + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21, + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21.Order, + }, + }, + }, + }, + }, + "Fail: Replacement order has lower GTB than existing order": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21, + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21.Order, + }, + }, + }, + }, + }, + "Fail: Replacement order has equal GTB to existing order": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB20, + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order, + }, + }, + }, + }, + }, + "Success: Replacement order after partial match in same block": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + *clobtypes.NewMsgPlaceOrder(MustScaleOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Bob_Num0, ClientId: 0, ClobPairId: 0}, + Side: clobtypes.Order_SIDE_SELL, + Quantums: 3, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, + }, + testapp.DefaultGenesis(), + )), + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21, + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.Quantums / 2, + }, + }, + }, + }, + }, + "Success: Replacement order increases size in next block after partial fill": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + *clobtypes.NewMsgPlaceOrder(MustScaleOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Bob_Num0, ClientId: 0, ClobPairId: 0}, + Side: clobtypes.Order_SIDE_SELL, + Quantums: 3, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, + }, + testapp.DefaultGenesis(), + )), + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.Quantums / 2, + }, + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.Quantums / 2, + }, + }, + }, + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy7_Price10_GTB21, + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy7_Price10_GTB21.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy7_Price10_GTB21.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.Quantums / 2, + }, + }, + }, + }, + }, + "Success: Replacement order swaps side in next block after partial fill": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + *clobtypes.NewMsgPlaceOrder(MustScaleOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Bob_Num0, ClientId: 0, ClobPairId: 0}, + Side: clobtypes.Order_SIDE_SELL, + Quantums: 3, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, + }, + testapp.DefaultGenesis(), + )), + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.Quantums / 2, + }, + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.Quantums / 2, + }, + }, + }, + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Sell6_Price10_GTB21, + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy7_Price10_GTB21.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Sell6_Price10_GTB21.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Sell6_Price10_GTB21.Order.Quantums / 2, + }, + }, + }, + }, + }, + "Success: Replacement order decreases size in next block after partial fill": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + *clobtypes.NewMsgPlaceOrder(MustScaleOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Bob_Num0, ClientId: 0, ClobPairId: 0}, + Side: clobtypes.Order_SIDE_SELL, + Quantums: 3, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, + }, + testapp.DefaultGenesis(), + )), + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.Quantums / 2, + }, + }, + }, + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21, + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB21.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.Quantums / 2, + }, + }, + }, + }, + }, + "Fail: Replacement order attempts to decrease size such that the order would be fully filled": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + *clobtypes.NewMsgPlaceOrder(MustScaleOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Bob_Num0, ClientId: 0, ClobPairId: 0}, + Side: clobtypes.Order_SIDE_SELL, + Quantums: 3, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, + }, + testapp.DefaultGenesis(), + )), + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.Quantums / 2, + }, + }, + }, + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + *clobtypes.NewMsgPlaceOrder(MustScaleOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Alice_Num0, ClientId: 0, ClobPairId: 0}, + Side: clobtypes.Order_SIDE_BUY, + Quantums: 3, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, + }, + testapp.DefaultGenesis(), + )), + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.Quantums / 2, + }, + }, + }, + }, + }, + "Fail: Replacement order attempts to decrease size below partially filled amount": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + *clobtypes.NewMsgPlaceOrder(MustScaleOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Bob_Num0, ClientId: 0, ClobPairId: 0}, + Side: clobtypes.Order_SIDE_SELL, + Quantums: 3, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, + }, + testapp.DefaultGenesis(), + )), + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.Quantums / 2, + }, + }, + }, + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + *clobtypes.NewMsgPlaceOrder(MustScaleOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Alice_Num0, ClientId: 0, ClobPairId: 0}, + Side: clobtypes.Order_SIDE_BUY, + Quantums: 2, + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, + }, + testapp.DefaultGenesis(), + )), + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: true, + expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order, + expectedFillAmount: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.Quantums / 2, + }, + }, + }, + }, + }, + "Success: Replacing order with FOK which does not fully match results in order being removed from the book": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + fok_replacement, + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: false, + }, + }, + }, + }, + }, + "Success: Replacing order with IOC which does not fully match results in order being removed from the book": { + blocks: []blockOrdersAndExpectations{ + { + ordersToPlace: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, + ioc_replacement, + }, + orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { + shouldExistOnMemclob: false, + }, + }, + }, + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + + for i, block := range tc.blocks { + for _, order := range block.ordersToPlace { + for _, checkTx := range testapp.MustMakeCheckTxsWithClobMsg(ctx, tApp.App, order) { + tApp.CheckTx(checkTx) + } + } + + for orderId, expectations := range block.orderIdsExpectations { + order, exists := tApp.App.ClobKeeper.MemClob.GetOrder(ctx, orderId) + require.Equal(t, expectations.shouldExistOnMemclob, exists) + if expectations.shouldExistOnMemclob { + require.Equal(t, expectations.expectedOrder, order) + } + _, fillAmount, _ := tApp.App.ClobKeeper.GetOrderFillAmount(ctx, orderId) + require.Equal(t, expectations.expectedFillAmount, uint64(fillAmount)) + } + + ctx = tApp.AdvanceToBlock(uint32(i+2), testapp.AdvanceToBlockOptions{}) + } + }) + } +} diff --git a/protocol/x/clob/types/message_proposed_operations.go b/protocol/x/clob/types/message_proposed_operations.go index b616de344b..8e3916e7fd 100644 --- a/protocol/x/clob/types/message_proposed_operations.go +++ b/protocol/x/clob/types/message_proposed_operations.go @@ -201,8 +201,6 @@ func (validator *operationsQueueValidator) validateShortTermOrderPlacementOperat ) } // Replacement Orders have a higher priority than the previously placed order that it replaces. - // All short term replacement orders should be checked here. Note that for long term orders, - // this check only takes effect if the order being replaced is in the same block. if prevOrder.MustCmpReplacementOrder(&order) != -1 { return errorsmod.Wrapf( ErrInvalidReplacement,