Skip to content

Commit

Permalink
disable deactivating vaults with positive equity (#2301)
Browse files Browse the repository at this point in the history
  • Loading branch information
tqin7 authored Sep 20, 2024
1 parent cee80fc commit dde2216
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func TestMsgWithdrawFromMegavault(t *testing.T) {
{
id: constants.Vault_Clob0,
params: vaulttypes.VaultParams{
Status: vaulttypes.VaultStatus_VAULT_STATUS_DEACTIVATED,
Status: vaulttypes.VaultStatus_VAULT_STATUS_STAND_BY,
},
assetQuoteQuantums: big.NewInt(400),
positionBaseQuantums: big.NewInt(0),
Expand Down
4 changes: 2 additions & 2 deletions protocol/x/vault/keeper/orders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func TestRefreshAllVaultOrders(t *testing.T) {
},
activationThresholdQuoteQuantums: big.NewInt(1_000_000_000),
},
"Two Vaults, One Stand-By, One Deactivated, Both Above Activation Threshold": {
"Two Vaults, One Stand-By, One Deactivated, One Above Activation Threshold": {
vaultIds: []vaulttypes.VaultId{
constants.Vault_Clob0,
constants.Vault_Clob1,
Expand All @@ -77,7 +77,7 @@ func TestRefreshAllVaultOrders(t *testing.T) {
},
assetQuantums: []*big.Int{
big.NewInt(1_000_000_000), // 1,000 USDC
big.NewInt(1_000_000_001),
big.NewInt(0),
},
activationThresholdQuoteQuantums: big.NewInt(1_000_000_000),
},
Expand Down
10 changes: 10 additions & 0 deletions protocol/x/vault/keeper/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ func (k Keeper) SetVaultParams(
return err
}

if vaultParams.Status == types.VaultStatus_VAULT_STATUS_DEACTIVATED {
vaultEquity, err := k.GetVaultEquity(ctx, vaultId)
if err != nil {
return err
}
if vaultEquity.Sign() > 0 {
return types.ErrDeactivatePositiveEquityVault
}
}

b := k.cdc.MustMarshal(&vaultParams)
store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte(types.VaultParamsKeyPrefix))
store.Set(vaultId.ToStateKey(), b)
Expand Down
149 changes: 122 additions & 27 deletions protocol/x/vault/keeper/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper_test
import (
"testing"

"github.com/cometbft/cometbft/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/gogoproto/proto"
"github.com/dydxprotocol/v4-chain/protocol/dtypes"
Expand All @@ -13,8 +14,9 @@ import (
v1 "github.com/dydxprotocol/v4-chain/protocol/indexer/protocol/v1"
testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app"
"github.com/dydxprotocol/v4-chain/protocol/testutil/constants"
satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types"
"github.com/dydxprotocol/v4-chain/protocol/x/vault/keeper"
"github.com/dydxprotocol/v4-chain/protocol/x/vault/types"
vaulttypes "github.com/dydxprotocol/v4-chain/protocol/x/vault/types"
"github.com/stretchr/testify/require"
)

Expand All @@ -25,10 +27,10 @@ func TestGetSetDefaultQuotingParams(t *testing.T) {

// Params should have default values at genesis.
params := k.GetDefaultQuotingParams(ctx)
require.Equal(t, types.DefaultQuotingParams(), params)
require.Equal(t, vaulttypes.DefaultQuotingParams(), params)

// Set new params and get.
newParams := types.QuotingParams{
newParams := vaulttypes.QuotingParams{
Layers: 3,
SpreadMinPpm: 4_000,
SpreadBufferPpm: 2_000,
Expand All @@ -42,7 +44,7 @@ func TestGetSetDefaultQuotingParams(t *testing.T) {
require.Equal(t, newParams, k.GetDefaultQuotingParams(ctx))

// Set invalid params and get.
invalidParams := types.QuotingParams{
invalidParams := vaulttypes.QuotingParams{
Layers: 3,
SpreadMinPpm: 4_000,
SpreadBufferPpm: 2_000,
Expand All @@ -59,9 +61,15 @@ func TestGetSetDefaultQuotingParams(t *testing.T) {
func TestGetSetVaultParams(t *testing.T) {
tests := map[string]struct {
// Vault id.
vaultId types.VaultId
vaultId vaulttypes.VaultId
// Existing vault params, if any.
existingVaultParams *vaulttypes.VaultParams
// Asset quote quantums that vault has.
assetQuoteQuantums int64
// Position base quantums that vault has.
positionBaseQuantums int64
// Vault params to set.
vaultParams *types.VaultParams
vaultParams *vaulttypes.VaultParams
// Expected on-chain indexer events
expectedIndexerEvents []*indexerevents.UpsertVaultEventV1
// Expected error.
Expand Down Expand Up @@ -89,24 +97,64 @@ func TestGetSetVaultParams(t *testing.T) {
},
},
},
"Success - Non-existent Vault Params": {
vaultId: constants.Vault_Clob1,
vaultParams: nil,
"Success - Deactivate a vault with zero equity": {
vaultId: constants.Vault_Clob1,
existingVaultParams: &constants.VaultParams,
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 negative equity": {
vaultId: constants.Vault_Clob1,
existingVaultParams: &constants.VaultParams,
assetQuoteQuantums: 0,
positionBaseQuantums: -1,
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,
),
},
},
},
"Failure - Deactivate a vault with positive equity": {
vaultId: constants.Vault_Clob1,
existingVaultParams: &constants.VaultParams,
assetQuoteQuantums: 0,
positionBaseQuantums: 1,
vaultParams: &vaulttypes.VaultParams{
Status: vaulttypes.VaultStatus_VAULT_STATUS_DEACTIVATED,
},
expectedErr: vaulttypes.ErrDeactivatePositiveEquityVault,
},
"Failure - Unspecified Status": {
vaultId: constants.Vault_Clob0,
vaultParams: &types.VaultParams{
vaultParams: &vaulttypes.VaultParams{
QuotingParams: &constants.QuotingParams,
},
expectedErr: types.ErrUnspecifiedVaultStatus,
expectedErr: vaulttypes.ErrUnspecifiedVaultStatus,
},
"Failure - Invalid Quoting Params": {
vaultId: constants.Vault_Clob0,
vaultParams: &types.VaultParams{
Status: types.VaultStatus_VAULT_STATUS_STAND_BY,
vaultParams: &vaulttypes.VaultParams{
Status: vaulttypes.VaultStatus_VAULT_STATUS_STAND_BY,
QuotingParams: &constants.InvalidQuotingParams,
},
expectedErr: types.ErrInvalidOrderExpirationSeconds,
expectedErr: vaulttypes.ErrInvalidOrderExpirationSeconds,
},
}

Expand All @@ -116,21 +164,68 @@ func TestGetSetVaultParams(t *testing.T) {
appOpts := map[string]interface{}{
indexer.MsgSenderInstanceForTest: msgSender,
}
tApp := testapp.NewTestAppBuilder(t).WithAppOptions(appOpts).Build()
tApp := testapp.NewTestAppBuilder(t).WithAppOptions(appOpts).
WithGenesisDocFn(func() (genesis types.GenesisDoc) {
genesis = testapp.DefaultGenesis()
testapp.UpdateGenesisDocWithAppStateForModule(
&genesis,
func(genesisState *vaulttypes.GenesisState) {
if tc.existingVaultParams != nil {
genesisState.Vaults = []vaulttypes.Vault{
{
VaultId: tc.vaultId,
VaultParams: *tc.existingVaultParams,
},
}
}
},
)
testapp.UpdateGenesisDocWithAppStateForModule(
&genesis,
func(genesisState *satypes.GenesisState) {
assetPositions := []*satypes.AssetPosition{}
if tc.assetQuoteQuantums != 0 {
assetPositions = append(assetPositions, &satypes.AssetPosition{
AssetId: constants.Usdc.GetId(),
Quantums: dtypes.NewInt(tc.assetQuoteQuantums),
})
}
perpPositions := []*satypes.PerpetualPosition{}
if tc.positionBaseQuantums != 0 {
perpPositions = append(perpPositions, &satypes.PerpetualPosition{
PerpetualId: tc.vaultId.Number,
Quantums: dtypes.NewInt(tc.positionBaseQuantums),
})
}
genesisState.Subaccounts = []satypes.Subaccount{
{
Id: tc.vaultId.ToSubaccountId(),
AssetPositions: assetPositions,
PerpetualPositions: perpPositions,
},
}
},
)
return genesis
}).Build()
ctx := tApp.InitChain()
k := tApp.App.VaultKeeper

if tc.vaultParams == nil {
if tc.existingVaultParams == nil {
_, exists := k.GetVaultParams(ctx, tc.vaultId)
require.False(t, exists)
return
}

err := k.SetVaultParams(ctx, tc.vaultId, *tc.vaultParams)
if tc.expectedErr != nil {
require.ErrorIs(t, err, tc.expectedErr)
_, exists := k.GetVaultParams(ctx, tc.vaultId)
require.False(t, exists)
v, exists := k.GetVaultParams(ctx, tc.vaultId)
if tc.existingVaultParams == nil {
require.False(t, exists)
} else {
require.True(t, exists)
require.Equal(t, *tc.existingVaultParams, v)
}
} else {
require.NoError(t, err)
p, exists := k.GetVaultParams(ctx, tc.vaultId)
Expand All @@ -150,17 +245,17 @@ func TestGetVaultQuotingParams(t *testing.T) {
tests := map[string]struct {
/* Setup */
// Vault id.
vaultId types.VaultId
vaultId vaulttypes.VaultId
// Vault params to set.
vaultParams *types.VaultParams
vaultParams *vaulttypes.VaultParams
/* Expectations */
// Whether quoting params should be default.
shouldBeDefault bool
}{
"Default Quoting Params": {
vaultId: constants.Vault_Clob0,
vaultParams: &types.VaultParams{
Status: types.VaultStatus_VAULT_STATUS_CLOSE_ONLY,
vaultParams: &vaulttypes.VaultParams{
Status: vaulttypes.VaultStatus_VAULT_STATUS_CLOSE_ONLY,
},
shouldBeDefault: true,
},
Expand All @@ -187,7 +282,7 @@ func TestGetVaultQuotingParams(t *testing.T) {
p, exists := k.GetVaultQuotingParams(ctx, tc.vaultId)
require.True(t, exists)
if tc.shouldBeDefault {
require.Equal(t, types.DefaultQuotingParams(), p)
require.Equal(t, vaulttypes.DefaultQuotingParams(), p)
} else {
require.Equal(t, *tc.vaultParams.QuotingParams, p)
}
Expand All @@ -208,22 +303,22 @@ func TestGetSetOperatorParams(t *testing.T) {
params := k.GetOperatorParams(ctx)
require.Equal(
t,
types.OperatorParams{
vaulttypes.OperatorParams{
Operator: constants.GovAuthority,
},
params,
)

// Set operator to Alice.
newParams := types.OperatorParams{
newParams := vaulttypes.OperatorParams{
Operator: constants.AliceAccAddress.String(),
}
err := k.SetOperatorParams(ctx, newParams)
require.NoError(t, err)
require.Equal(t, newParams, k.GetOperatorParams(ctx))

// Set invalid operator and get.
invalidParams := types.OperatorParams{
invalidParams := vaulttypes.OperatorParams{
Operator: "",
}
err = k.SetOperatorParams(ctx, invalidParams)
Expand Down
8 changes: 4 additions & 4 deletions protocol/x/vault/keeper/vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestDecommissionNonPositiveEquityVaults(t *testing.T) {
},
statuses: []vaulttypes.VaultStatus{
vaulttypes.VaultStatus_VAULT_STATUS_QUOTING,
vaulttypes.VaultStatus_VAULT_STATUS_DEACTIVATED,
vaulttypes.VaultStatus_VAULT_STATUS_STAND_BY,
},
equities: []*big.Int{
big.NewInt(1),
Expand Down Expand Up @@ -464,7 +464,7 @@ func TestGetMegavaultEquity(t *testing.T) {
},
expectedMegavaultEquity: big.NewInt(1_345),
},
"Megavault subaccount with 1000 equity, One quoting vault with 345 equity, One deactivated vault with 5 equity,": {
"Megavault subaccount with 1000 equity, One quoting vault with 345 equity, One deactivated vault with -5 equity,": {
megavaultSaEquity: big.NewInt(1_000),
vaults: []vaulttypes.Vault{
{
Expand All @@ -482,9 +482,9 @@ func TestGetMegavaultEquity(t *testing.T) {
},
vaultEquities: []*big.Int{
big.NewInt(345),
big.NewInt(5),
big.NewInt(-5),
},
expectedMegavaultEquity: big.NewInt(1_350),
expectedMegavaultEquity: big.NewInt(1_345),
},
}
for name, tc := range tests {
Expand Down
5 changes: 5 additions & 0 deletions protocol/x/vault/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,9 @@ var (
28,
"Insufficient redeemed quote quantums",
)
ErrDeactivatePositiveEquityVault = errorsmod.Register(
ModuleName,
29,
"Cannot deactivate vaults with positive equity",
)
)

0 comments on commit dde2216

Please sign in to comment.