From 53cca7911efb969726ae1028c4b2bd5fac4bb931 Mon Sep 17 00:00:00 2001 From: Tian Date: Mon, 23 Sep 2024 10:08:26 -0400 Subject: [PATCH] cancel existing orders when setting a vault to deactivated or stand-by (#2310) --- protocol/x/vault/keeper/orders.go | 41 +++++++++-- protocol/x/vault/keeper/orders_test.go | 94 ++++++++++++++++++++++++++ protocol/x/vault/keeper/params.go | 19 ++++++ protocol/x/vault/keeper/params_test.go | 85 ++++++++++++++++++++++- 4 files changed, 231 insertions(+), 8 deletions(-) diff --git a/protocol/x/vault/keeper/orders.go b/protocol/x/vault/keeper/orders.go index 1cbb1344bf..1f184d9f76 100644 --- a/protocol/x/vault/keeper/orders.go +++ b/protocol/x/vault/keeper/orders.go @@ -133,13 +133,20 @@ func (k Keeper) RefreshVaultClobOrders(ctx sdk.Context, vaultId types.VaultId) ( return types.ErrVaultParamsNotFound } for i := len(ordersToPlace); i < len(mostRecentClientIds); i++ { - orderId := vaultId.GetClobOrderId(mostRecentClientIds[i]) - _, exists := k.clobKeeper.GetLongTermOrderPlacement(ctx, *orderId) - if exists { - err := k.CancelVaultClobOrder(ctx, vaultId, orderId, quotingParams.OrderExpirationSeconds) - if err != nil { - log.ErrorLogWithError(ctx, "Failed to cancel vault clob order", err, "vaultId", vaultId) - } + _, err = k.TryToCancelVaultClobOrder( + ctx, + vaultId, + mostRecentClientIds[i], + quotingParams.OrderExpirationSeconds, + ) + if err != nil { + log.ErrorLogWithError( + ctx, + "Failed to cancel no longer needed vault clob order", + err, + "vaultId", + vaultId, + ) } } @@ -473,6 +480,26 @@ func (k Keeper) CancelVaultClobOrder( return err } +// TryToCancelVaultClobOrder tries to cancel a vault CLOB order. Returns whether the order exists +// and whether cancellation errors. +func (k Keeper) TryToCancelVaultClobOrder( + ctx sdk.Context, + vaultId types.VaultId, + clientId uint32, + orderExpirationSeconds uint32, +) ( + orderExists bool, + err error, +) { + orderId := vaultId.GetClobOrderId(clientId) + _, exists := k.clobKeeper.GetLongTermOrderPlacement(ctx, *orderId) + if exists { + err = k.CancelVaultClobOrder(ctx, vaultId, orderId, orderExpirationSeconds) + return true, err + } + return false, nil +} + // ReplaceVaultClobOrder replaces a vault CLOB order internal to the protocol and // emits order replacement indexer event. func (k Keeper) ReplaceVaultClobOrder( diff --git a/protocol/x/vault/keeper/orders_test.go b/protocol/x/vault/keeper/orders_test.go index a7c3cd411c..e7941f4585 100644 --- a/protocol/x/vault/keeper/orders_test.go +++ b/protocol/x/vault/keeper/orders_test.go @@ -745,6 +745,100 @@ func TestRefreshVaultClobOrders(t *testing.T) { }, }, }, + "Success - Orders refresh due to status changing to stand-by. No more orders": { + vaultId: constants.Vault_Clob0, + instances: []Instance{ + { + advanceBlock: func(ctx sdk.Context, tApp *testapp.TestApp) sdk.Context { + msgSetVaultParams := vaulttypes.MsgSetVaultParams{ + Authority: constants.AliceAccAddress.String(), // operator + VaultId: constants.Vault_Clob0, + VaultParams: vaulttypes.VaultParams{ + Status: vaulttypes.VaultStatus_VAULT_STATUS_STAND_BY, + }, + } + CheckTx_MsgSetVaultParams := testapp.MustMakeCheckTx( + ctx, + tApp.App, + testapp.MustMakeCheckTxOptions{ + AccAddressForSigning: constants.AliceAccAddress.String(), + Gas: constants.TestGasLimit, + FeeAmt: constants.TestFeeCoins_5Cents, + }, + &msgSetVaultParams, + ) + checkTxResp := tApp.CheckTx(CheckTx_MsgSetVaultParams) + require.Conditionf(t, checkTxResp.IsOK, "Expected CheckTx to succeed. Response: %+v", checkTxResp) + + return tApp.AdvanceToBlock( + uint32(tApp.GetBlockHeight())+1, + testapp.AdvanceToBlockOptions{ + BlockTime: ctx.BlockTime().Add(time.Second * 2), + }, + ) + }, + ordersRefreshed: []bool{}, // no orders + orderSides: []clobtypes.Order_Side{}, + clientIdIsCanonical: []bool{}, + }, + }, + }, + "Success - Orders refresh due to retrieving to 0 equity and status changing to deactivated. No more orders": { + vaultId: constants.Vault_Clob0, + instances: []Instance{ + { + advanceBlock: func(ctx sdk.Context, tApp *testapp.TestApp) sdk.Context { + msgRetrieveFromVault := vaulttypes.MsgRetrieveFromVault{ + Authority: constants.AliceAccAddress.String(), // operator + VaultId: constants.Vault_Clob0, + QuoteQuantums: dtypes.NewInt(2_000_000_000), // retrieve all quote quantums the vault has. + } + CheckTx_MsgRetrieveFromVault := testapp.MustMakeCheckTx( + ctx, + tApp.App, + testapp.MustMakeCheckTxOptions{ + AccAddressForSigning: constants.AliceAccAddress.String(), + Gas: constants.TestGasLimit, + FeeAmt: constants.TestFeeCoins_5Cents, + }, + &msgRetrieveFromVault, + ) + checkTxResp := tApp.CheckTx(CheckTx_MsgRetrieveFromVault) + require.Conditionf(t, checkTxResp.IsOK, "Expected CheckTx to succeed. Response: %+v", checkTxResp) + + msgSetVaultParams := vaulttypes.MsgSetVaultParams{ + Authority: constants.AliceAccAddress.String(), // operator + VaultId: constants.Vault_Clob0, + VaultParams: vaulttypes.VaultParams{ + Status: vaulttypes.VaultStatus_VAULT_STATUS_DEACTIVATED, + }, + } + CheckTx_MsgSetVaultParams := testapp.MustMakeCheckTx( + ctx, + tApp.App, + testapp.MustMakeCheckTxOptions{ + AccAddressForSigning: constants.AliceAccAddress.String(), + Gas: constants.TestGasLimit, + FeeAmt: constants.TestFeeCoins_5Cents, + }, + &msgSetVaultParams, + ) + checkTxResp = tApp.CheckTx(CheckTx_MsgSetVaultParams) + require.Conditionf(t, checkTxResp.IsOK, "Expected CheckTx to succeed. Response: %+v", checkTxResp) + + return tApp.AdvanceToBlock( + uint32(tApp.GetBlockHeight())+1, + testapp.AdvanceToBlockOptions{ + BlockTime: ctx.BlockTime().Add(time.Second * 2), + }, + ) + }, + ordersRefreshed: []bool{}, // no orders + orderSides: []clobtypes.Order_Side{}, + clientIdIsCanonical: []bool{}, + }, + }, + }, "Success - Vault for non-existent Clob Pair 4321": { vaultId: vaulttypes.VaultId{ Type: vaulttypes.VaultType_VAULT_TYPE_CLOB, diff --git a/protocol/x/vault/keeper/params.go b/protocol/x/vault/keeper/params.go index edb37a1827..a054e9d057 100644 --- a/protocol/x/vault/keeper/params.go +++ b/protocol/x/vault/keeper/params.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" indexerevents "github.com/dydxprotocol/v4-chain/protocol/indexer/events" "github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager" + "github.com/dydxprotocol/v4-chain/protocol/lib/log" "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" ) @@ -78,6 +79,24 @@ func (k Keeper) SetVaultParams( } } + // When setting an existing vault to deactivated or stand-by, cancel any existing orders. + _, quotingParams, exists := k.GetVaultAndQuotingParams(ctx, vaultId) + if exists && (vaultParams.Status == types.VaultStatus_VAULT_STATUS_DEACTIVATED || + vaultParams.Status == types.VaultStatus_VAULT_STATUS_STAND_BY) { + mostRecentClientIds := k.GetMostRecentClientIds(ctx, vaultId) + for _, clientId := range mostRecentClientIds { + _, err := k.TryToCancelVaultClobOrder(ctx, vaultId, clientId, quotingParams.OrderExpirationSeconds) + if err != nil { + log.ErrorLogWithError( + ctx, + "Failed to cancel vault clob order when setting existing vault to deactivated or stand-by", + err, + ) + } + } + k.SetMostRecentClientIds(ctx, vaultId, []uint32{}) + } + b := k.cdc.MustMarshal(&vaultParams) store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte(types.VaultParamsKeyPrefix)) store.Set(vaultId.ToStateKey(), b) diff --git a/protocol/x/vault/keeper/params_test.go b/protocol/x/vault/keeper/params_test.go index cbd0419cff..3a22c7cec3 100644 --- a/protocol/x/vault/keeper/params_test.go +++ b/protocol/x/vault/keeper/params_test.go @@ -70,6 +70,10 @@ func TestGetSetVaultParams(t *testing.T) { positionBaseQuantums int64 // Vault params to set. vaultParams *vaulttypes.VaultParams + // Number of vault orders before setting vault params. + numVaultOrdersPreSet uint32 + // Number of vault orders after setting vault params. + numVaultOrdersPostSet uint32 // Expected on-chain indexer events expectedIndexerEvents []*indexerevents.UpsertVaultEventV1 // Expected error. @@ -97,6 +101,32 @@ func TestGetSetVaultParams(t *testing.T) { }, }, }, + "Success - Create a stand-by vault": { + vaultId: constants.Vault_Clob1, + vaultParams: &vaulttypes.VaultParams{ + Status: vaulttypes.VaultStatus_VAULT_STATUS_STAND_BY, + }, + expectedIndexerEvents: []*indexerevents.UpsertVaultEventV1{ + { + Address: constants.Vault_Clob1.ToModuleAccountAddress(), + ClobPairId: constants.Vault_Clob1.Number, + Status: v1.VaultStatusToIndexerVaultStatus(vaulttypes.VaultStatus_VAULT_STATUS_STAND_BY), + }, + }, + }, + "Success - Create a deactivated vault": { + vaultId: constants.Vault_Clob1, + vaultParams: &vaulttypes.VaultParams{ + Status: vaulttypes.VaultStatus_VAULT_STATUS_DEACTIVATED, + }, + expectedIndexerEvents: []*indexerevents.UpsertVaultEventV1{ + { + Address: constants.Vault_Clob1.ToModuleAccountAddress(), + ClobPairId: constants.Vault_Clob1.Number, + Status: v1.VaultStatusToIndexerVaultStatus(vaulttypes.VaultStatus_VAULT_STATUS_DEACTIVATED), + }, + }, + }, "Success - Deactivate a vault with zero equity": { vaultId: constants.Vault_Clob1, existingVaultParams: &constants.VaultParams, @@ -131,6 +161,27 @@ func TestGetSetVaultParams(t *testing.T) { }, }, }, + "Success - Put a quoting vault to stand-by should cancel all existing orders": { + vaultId: constants.Vault_Clob1, + existingVaultParams: &vaulttypes.VaultParams{ + Status: vaulttypes.VaultStatus_VAULT_STATUS_QUOTING, + }, + assetQuoteQuantums: 1_000_000_000, + vaultParams: &vaulttypes.VaultParams{ + Status: vaulttypes.VaultStatus_VAULT_STATUS_STAND_BY, + }, + numVaultOrdersPreSet: 4, + numVaultOrdersPostSet: 0, + expectedIndexerEvents: []*indexerevents.UpsertVaultEventV1{ + { + Address: constants.Vault_Clob1.ToModuleAccountAddress(), + ClobPairId: constants.Vault_Clob1.Number, + Status: v1.VaultStatusToIndexerVaultStatus( + vaulttypes.VaultStatus_VAULT_STATUS_STAND_BY, + ), + }, + }, + }, "Failure - Deactivate a vault with positive equity": { vaultId: constants.Vault_Clob1, existingVaultParams: &constants.VaultParams, @@ -215,8 +266,18 @@ func TestGetSetVaultParams(t *testing.T) { _, exists := k.GetVaultParams(ctx, tc.vaultId) require.False(t, exists) } + require.Len( + t, + tApp.App.ClobKeeper.GetAllStatefulOrders(ctx), + int(tc.numVaultOrdersPreSet), + ) + require.Len( + t, + k.GetMostRecentClientIds(ctx, tc.vaultId), + int(tc.numVaultOrdersPreSet), + ) - err := k.SetVaultParams(ctx, tc.vaultId, *tc.vaultParams) + err := k.SetVaultParams(ctx.WithIsCheckTx(false), tc.vaultId, *tc.vaultParams) if tc.expectedErr != nil { require.ErrorIs(t, err, tc.expectedErr) v, exists := k.GetVaultParams(ctx, tc.vaultId) @@ -226,11 +287,33 @@ func TestGetSetVaultParams(t *testing.T) { require.True(t, exists) require.Equal(t, *tc.existingVaultParams, v) } + + require.Len( + t, + tApp.App.ClobKeeper.GetAllStatefulOrders(ctx), + int(tc.numVaultOrdersPreSet), + ) + require.Len( + t, + k.GetMostRecentClientIds(ctx, tc.vaultId), + int(tc.numVaultOrdersPreSet), + ) } else { require.NoError(t, err) p, exists := k.GetVaultParams(ctx, tc.vaultId) require.True(t, exists) require.Equal(t, *tc.vaultParams, p) + + require.Len( + t, + tApp.App.ClobKeeper.GetAllStatefulOrders(ctx), + int(tc.numVaultOrdersPostSet), + ) + require.Len( + t, + k.GetMostRecentClientIds(ctx, tc.vaultId), + int(tc.numVaultOrdersPostSet), + ) } if tc.expectedErr == nil && tc.vaultParams != nil {