From 8f77dbe0003580dc21841be4bcc0ed3e5fed2b63 Mon Sep 17 00:00:00 2001 From: Jonathan Fung Date: Mon, 26 Feb 2024 17:15:19 -0800 Subject: [PATCH] validatebasic for new MsgBatchCancel --- protocol/x/clob/types/message_batch_cancel.go | 54 ++++++++ .../x/clob/types/message_batch_cancel_test.go | 123 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 protocol/x/clob/types/message_batch_cancel.go create mode 100644 protocol/x/clob/types/message_batch_cancel_test.go diff --git a/protocol/x/clob/types/message_batch_cancel.go b/protocol/x/clob/types/message_batch_cancel.go new file mode 100644 index 0000000000..08d0295704 --- /dev/null +++ b/protocol/x/clob/types/message_batch_cancel.go @@ -0,0 +1,54 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + types "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" +) + +var _ sdk.Msg = &MsgBatchCancel{} + +// NewMsgCancelOrderShortTerm constructs a MsgBatchCancel from an `OrderId` and a `GoodTilBlock`. +func NewMsgBatchCancel(subaccountId types.SubaccountId, cancelBatch []OrderBatch, goodTilBlock uint32) *MsgBatchCancel { + return &MsgBatchCancel{ + SubaccountId: subaccountId, + ShortTermCancels: cancelBatch, + GoodTilBlock: goodTilBlock, + } +} + +// ValidateBasic performs stateless validation for +func (msg *MsgBatchCancel) ValidateBasic() (err error) { + subaccountId := msg.GetSubaccountId() + if err := subaccountId.Validate(); err != nil { + return err + } + + cancelBatches := msg.GetShortTermCancels() + totalNumberCancels := 0 + for _, cancelBatch := range cancelBatches { + totalNumberCancels += len(cancelBatch.GetClientIds()) + seenClientIds := map[uint32]struct{}{} + for _, clientId := range cancelBatch.GetClientIds() { + if _, seen := seenClientIds[clientId]; seen { + return errorsmod.Wrapf( + ErrInvalidBatchCancel, + "Batch cancel cannot have duplicate cancels. Duplicate clob pair id: %+v, client id: %+v", + cancelBatch.GetClobPairId(), + clientId, + ) + } + seenClientIds[clientId] = struct{}{} + } + } + if uint32(totalNumberCancels) > MaxMsgBatchCancelBatchSize { + return errorsmod.Wrapf( + ErrInvalidBatchCancel, + "Batch cancel cannot have over %+v orders. Order count: %+v", + MaxMsgBatchCancelBatchSize, + totalNumberCancels, + ) + } + return nil +} diff --git a/protocol/x/clob/types/message_batch_cancel_test.go b/protocol/x/clob/types/message_batch_cancel_test.go new file mode 100644 index 0000000000..acf3afe404 --- /dev/null +++ b/protocol/x/clob/types/message_batch_cancel_test.go @@ -0,0 +1,123 @@ +package types_test + +import ( + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" + satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + "github.com/stretchr/testify/require" +) + +func TestMsgBatchCancel_ValidateBasic(t *testing.T) { + oneOverMax := []uint32{} + for i := uint32(0); i < types.MaxMsgBatchCancelBatchSize+1; i++ { + oneOverMax = append(oneOverMax, i) + } + + tests := map[string]struct { + msg types.MsgBatchCancel + err error + }{ + "invalid subaccount": { + msg: *types.NewMsgBatchCancel( + constants.InvalidSubaccountIdNumber, + []types.OrderBatch{ + { + ClobPairId: 0, + ClientIds: []uint32{ + 0, 1, 2, 3, + }, + }, + }, + 10, + ), + err: satypes.ErrInvalidSubaccountIdNumber, + }, + "over 100 cancels in for one clob pair id": { + msg: *types.NewMsgBatchCancel( + constants.Alice_Num0, + []types.OrderBatch{ + { + ClobPairId: 0, + ClientIds: oneOverMax, + }, + }, + 10, + ), + err: types.ErrInvalidBatchCancel, + }, + "over 100 cancels split over two clob pair id": { + msg: *types.NewMsgBatchCancel( + constants.Alice_Num0, + []types.OrderBatch{ + { + ClobPairId: 0, + ClientIds: oneOverMax[:52], + }, + { + ClobPairId: 1, + ClientIds: oneOverMax[:52], + }, + }, + 10, + ), + err: types.ErrInvalidBatchCancel, + }, + "success: two clob pair id, 100 cancels": { + msg: *types.NewMsgBatchCancel( + constants.Alice_Num0, + []types.OrderBatch{ + { + ClobPairId: 0, + ClientIds: oneOverMax[:50], + }, + { + ClobPairId: 1, + ClientIds: oneOverMax[:50], + }, + }, + 10, + ), + err: nil, + }, + "success: one clob pair id, 100 cancels": { + msg: *types.NewMsgBatchCancel( + constants.Alice_Num0, + []types.OrderBatch{ + { + ClobPairId: 0, + ClientIds: oneOverMax[:types.MaxMsgBatchCancelBatchSize], + }, + }, + 10, + ), + err: nil, + }, + "duplicate clob pair ids": { + msg: *types.NewMsgBatchCancel( + constants.Alice_Num0, + []types.OrderBatch{ + { + ClobPairId: 0, + ClientIds: []uint32{ + 0, 1, 2, 3, 1, + }, + }, + }, + 10, + ), + err: types.ErrInvalidBatchCancel, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.err != nil { + require.ErrorIs(t, err, tc.err) + return + } + require.NoError(t, err) + }) + } +}