Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CLOB-1046] populate negative tnc subaccounts in grpc request #890

Merged
merged 2 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions protocol/daemons/liquidation/client/grpc_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import (
"github.com/cosmos/cosmos-sdk/types/grpc"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/dydxprotocol/v4-chain/protocol/daemons/liquidation/api"
"github.com/dydxprotocol/v4-chain/protocol/lib"
"github.com/dydxprotocol/v4-chain/protocol/lib/metrics"
blocktimetypes "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/types"
clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types"
perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types"
pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types"
satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types"
Expand Down Expand Up @@ -205,7 +207,10 @@ func (c *Client) GetAllSubaccounts(
// subaccount ids to a gRPC server via `LiquidateSubaccounts`.
func (c *Client) SendLiquidatableSubaccountIds(
ctx context.Context,
subaccountIds []satypes.SubaccountId,
blockHeight uint32,
liquidatableSubaccountIds []satypes.SubaccountId,
negativeTncSubaccountIds []satypes.SubaccountId,
openPositionInfoMap map[uint32]*clobtypes.SubaccountOpenPositionInfo,
) error {
defer telemetry.ModuleMeasureSince(
metrics.LiquidationDaemon,
Expand All @@ -216,13 +221,31 @@ func (c *Client) SendLiquidatableSubaccountIds(

telemetry.ModuleSetGauge(
metrics.LiquidationDaemon,
float32(len(subaccountIds)),
float32(len(liquidatableSubaccountIds)),
metrics.LiquidatableSubaccountIds,
metrics.Count,
)
telemetry.ModuleSetGauge(
metrics.LiquidationDaemon,
float32(len(negativeTncSubaccountIds)),
metrics.NegativeTncSubaccountIds,
metrics.Count,
)

// Convert the map to a slice.
// Note that sorting here is not strictly necessary but is done for safety and to avoid making
// any assumptions on the server side.
sortedPerpetualIds := lib.GetSortedKeys[lib.Sortable[uint32]](openPositionInfoMap)
subaccountOpenPositionInfo := make([]clobtypes.SubaccountOpenPositionInfo, 0)
for _, perpetualId := range sortedPerpetualIds {
subaccountOpenPositionInfo = append(subaccountOpenPositionInfo, *openPositionInfoMap[perpetualId])
}

request := &api.LiquidateSubaccountsRequest{
LiquidatableSubaccountIds: subaccountIds,
BlockHeight: blockHeight,
LiquidatableSubaccountIds: liquidatableSubaccountIds,
NegativeTncSubaccountIds: negativeTncSubaccountIds,
SubaccountOpenPositionInfo: subaccountOpenPositionInfo,
}

if _, err := c.LiquidationServiceClient.LiquidateSubaccounts(ctx, request); err != nil {
Expand All @@ -231,7 +254,6 @@ func (c *Client) SendLiquidatableSubaccountIds(
return nil
}

// nolint:unused
func newContextWithQueryBlockHeight(
ctx context.Context,
blockHeight uint32,
Expand Down
79 changes: 65 additions & 14 deletions protocol/daemons/liquidation/client/grpc_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/dydxprotocol/v4-chain/protocol/testutil/constants"
"github.com/dydxprotocol/v4-chain/protocol/testutil/grpc"
blocktimetypes "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/types"
clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types"
perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types"
pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types"
satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types"
Expand Down Expand Up @@ -452,56 +453,106 @@ func TestGetAllMarketPrices(t *testing.T) {
func TestSendLiquidatableSubaccountIds(t *testing.T) {
tests := map[string]struct {
// mocks
setupMocks func(ctx context.Context, mck *mocks.QueryClient, ids []satypes.SubaccountId)
subaccountIds []satypes.SubaccountId
setupMocks func(context.Context, *mocks.QueryClient)
liquidatableSubaccountIds []satypes.SubaccountId
negativeTncSubaccountIds []satypes.SubaccountId
subaccountOpenPositionInfo map[uint32]*clobtypes.SubaccountOpenPositionInfo

// expectations
expectedError error
}{
"Success": {
setupMocks: func(ctx context.Context, mck *mocks.QueryClient, ids []satypes.SubaccountId) {
setupMocks: func(ctx context.Context, mck *mocks.QueryClient) {
req := &api.LiquidateSubaccountsRequest{
LiquidatableSubaccountIds: ids,
BlockHeight: uint32(50),
LiquidatableSubaccountIds: []satypes.SubaccountId{constants.Alice_Num0, constants.Bob_Num0},
NegativeTncSubaccountIds: []satypes.SubaccountId{constants.Carl_Num0, constants.Dave_Num0},
SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{
{
PerpetualId: 0,
SubaccountsWithLongPosition: []satypes.SubaccountId{
constants.Alice_Num0,
constants.Carl_Num0,
},
SubaccountsWithShortPosition: []satypes.SubaccountId{
constants.Bob_Num0,
constants.Dave_Num0,
},
},
},
}
response := &api.LiquidateSubaccountsResponse{}
mck.On("LiquidateSubaccounts", ctx, req).Return(response, nil)
},
subaccountIds: []satypes.SubaccountId{
liquidatableSubaccountIds: []satypes.SubaccountId{
constants.Alice_Num0,
constants.Bob_Num0,
},
negativeTncSubaccountIds: []satypes.SubaccountId{
constants.Carl_Num0,
constants.Dave_Num0,
},
subaccountOpenPositionInfo: map[uint32]*clobtypes.SubaccountOpenPositionInfo{
0: {
PerpetualId: 0,
SubaccountsWithLongPosition: []satypes.SubaccountId{
constants.Alice_Num0,
constants.Carl_Num0,
},
SubaccountsWithShortPosition: []satypes.SubaccountId{
constants.Bob_Num0,
constants.Dave_Num0,
},
},
},
},
"Success Empty": {
setupMocks: func(ctx context.Context, mck *mocks.QueryClient, ids []satypes.SubaccountId) {
setupMocks: func(ctx context.Context, mck *mocks.QueryClient) {
req := &api.LiquidateSubaccountsRequest{
LiquidatableSubaccountIds: ids,
BlockHeight: uint32(50),
LiquidatableSubaccountIds: []satypes.SubaccountId{},
NegativeTncSubaccountIds: []satypes.SubaccountId{},
SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{},
}
response := &api.LiquidateSubaccountsResponse{}
mck.On("LiquidateSubaccounts", ctx, req).Return(response, nil)
},
subaccountIds: []satypes.SubaccountId{},
liquidatableSubaccountIds: []satypes.SubaccountId{},
negativeTncSubaccountIds: []satypes.SubaccountId{},
subaccountOpenPositionInfo: map[uint32]*clobtypes.SubaccountOpenPositionInfo{},
},
"Errors are propagated": {
setupMocks: func(ctx context.Context, mck *mocks.QueryClient, ids []satypes.SubaccountId) {
setupMocks: func(ctx context.Context, mck *mocks.QueryClient) {
req := &api.LiquidateSubaccountsRequest{
LiquidatableSubaccountIds: ids,
BlockHeight: uint32(50),
LiquidatableSubaccountIds: []satypes.SubaccountId{},
NegativeTncSubaccountIds: []satypes.SubaccountId{},
SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{},
}
mck.On("LiquidateSubaccounts", ctx, req).Return(nil, errors.New("test error"))
},
subaccountIds: []satypes.SubaccountId{},
expectedError: errors.New("test error"),
liquidatableSubaccountIds: []satypes.SubaccountId{},
negativeTncSubaccountIds: []satypes.SubaccountId{},
subaccountOpenPositionInfo: map[uint32]*clobtypes.SubaccountOpenPositionInfo{},
expectedError: errors.New("test error"),
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
queryClientMock := &mocks.QueryClient{}
tc.setupMocks(grpc.Ctx, queryClientMock, tc.subaccountIds)
tc.setupMocks(grpc.Ctx, queryClientMock)

daemon := client.NewClient(log.NewNopLogger())
daemon.LiquidationServiceClient = queryClientMock

err := daemon.SendLiquidatableSubaccountIds(grpc.Ctx, tc.subaccountIds)
err := daemon.SendLiquidatableSubaccountIds(
grpc.Ctx,
uint32(50),
tc.liquidatableSubaccountIds,
tc.negativeTncSubaccountIds,
tc.subaccountOpenPositionInfo,
)
require.Equal(t, tc.expectedError, err)
})
}
Expand Down
94 changes: 82 additions & 12 deletions protocol/daemons/liquidation/client/sub_task_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/dydxprotocol/v4-chain/protocol/lib/metrics"
assetstypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types"
clobkeeper "github.com/dydxprotocol/v4-chain/protocol/x/clob/keeper"
clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types"
perpkeeper "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/keeper"
perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types"
pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types"
Expand Down Expand Up @@ -68,7 +69,9 @@ func (s *SubTaskRunnerImpl) RunLiquidationDaemonTaskLoop(
}

// 2. Check collateralization statuses of subaccounts with at least one open position.
liquidatableSubaccountIds, err := daemonClient.GetLiquidatableSubaccountIds(
liquidatableSubaccountIds,
negativeTncSubaccountIds,
err := daemonClient.GetLiquidatableSubaccountIds(
subaccounts,
marketPrices,
perpetuals,
Expand All @@ -78,8 +81,17 @@ func (s *SubTaskRunnerImpl) RunLiquidationDaemonTaskLoop(
return err
}

// Build a map of perpetual id to subaccounts with open positions in that perpetual.
subaccountOpenPositionInfo := daemonClient.GetSubaccountOpenPositionInfo(subaccounts)

// 3. Send the list of liquidatable subaccount ids to the daemon server.
err = daemonClient.SendLiquidatableSubaccountIds(ctx, liquidatableSubaccountIds)
err = daemonClient.SendLiquidatableSubaccountIds(
ctx,
lastCommittedBlockHeight,
liquidatableSubaccountIds,
negativeTncSubaccountIds,
subaccountOpenPositionInfo,
)
if err != nil {
return err
}
Expand Down Expand Up @@ -159,6 +171,7 @@ func (c *Client) GetLiquidatableSubaccountIds(
liquidityTiers map[uint32]perptypes.LiquidityTier,
) (
liquidatableSubaccountIds []satypes.SubaccountId,
negativeTncSubaccountIds []satypes.SubaccountId,
err error,
) {
defer telemetry.ModuleMeasureSince(
Expand All @@ -168,29 +181,83 @@ func (c *Client) GetLiquidatableSubaccountIds(
metrics.Latency,
)

numSubaccountsWithOpenPositions := 0
liquidatableSubaccountIds = make([]satypes.SubaccountId, 0)
negativeTncSubaccountIds = make([]satypes.SubaccountId, 0)
for _, subaccount := range subaccounts {
// Skip subaccounts with no open positions.
if len(subaccount.PerpetualPositions) == 0 {
continue
}

// Check if the subaccount is liquidatable.
isLiquidatable, err := c.CheckSubaccountCollateralization(
isLiquidatable, hasNegativeTnc, err := c.CheckSubaccountCollateralization(
subaccount,
marketPrices,
perpetuals,
liquidityTiers,
)
if err != nil {
c.logger.Error("Error checking collateralization status", "error", err)
return nil, err
return nil, nil, err
}

if isLiquidatable {
liquidatableSubaccountIds = append(liquidatableSubaccountIds, *subaccount.Id)
}
if hasNegativeTnc {
negativeTncSubaccountIds = append(negativeTncSubaccountIds, *subaccount.Id)
}
}

return liquidatableSubaccountIds, negativeTncSubaccountIds, nil
}

// GetSubaccountOpenPositionInfo iterates over the given subaccounts and returns a map of
// perpetual id to open position info.
func (c *Client) GetSubaccountOpenPositionInfo(
subaccounts []satypes.Subaccount,
) (
subaccountOpenPositionInfo map[uint32]*clobtypes.SubaccountOpenPositionInfo,
) {
defer telemetry.ModuleMeasureSince(
metrics.LiquidationDaemon,
time.Now(),
metrics.GetSubaccountOpenPositionInfo,
metrics.Latency,
)

numSubaccountsWithOpenPositions := 0
subaccountOpenPositionInfo = make(map[uint32]*clobtypes.SubaccountOpenPositionInfo)
for _, subaccount := range subaccounts {
// Skip subaccounts with no open positions.
if len(subaccount.PerpetualPositions) == 0 {
continue
}

for _, perpetualPosition := range subaccount.PerpetualPositions {
openPositionInfo, ok := subaccountOpenPositionInfo[perpetualPosition.PerpetualId]
if !ok {
openPositionInfo = &clobtypes.SubaccountOpenPositionInfo{
PerpetualId: perpetualPosition.PerpetualId,
SubaccountsWithLongPosition: make([]satypes.SubaccountId, 0),
SubaccountsWithShortPosition: make([]satypes.SubaccountId, 0),
}
subaccountOpenPositionInfo[perpetualPosition.PerpetualId] = openPositionInfo
}

if perpetualPosition.GetIsLong() {
openPositionInfo.SubaccountsWithLongPosition = append(
openPositionInfo.SubaccountsWithLongPosition,
*subaccount.Id,
)
} else {
openPositionInfo.SubaccountsWithShortPosition = append(
openPositionInfo.SubaccountsWithShortPosition,
*subaccount.Id,
)
}
}

numSubaccountsWithOpenPositions++
}

Expand All @@ -201,7 +268,7 @@ func (c *Client) GetLiquidatableSubaccountIds(
metrics.Count,
)

return liquidatableSubaccountIds, nil
return subaccountOpenPositionInfo
}

// CheckSubaccountCollateralization performs the same collateralization check as the application
Expand All @@ -216,6 +283,7 @@ func (c *Client) CheckSubaccountCollateralization(
liquidityTiers map[uint32]perptypes.LiquidityTier,
) (
isLiquidatable bool,
hasNegativeTnc bool,
err error,
) {
defer telemetry.ModuleMeasureSince(
Expand All @@ -232,7 +300,7 @@ func (c *Client) CheckSubaccountCollateralization(
perpetuals,
)
if err != nil {
return false, err
return false, false, err
}

bigTotalNetCollateral := big.NewInt(0)
Expand All @@ -242,7 +310,7 @@ func (c *Client) CheckSubaccountCollateralization(
// Note that we only expect USDC before multi-collateral support is added.
for _, assetPosition := range settledSubaccount.AssetPositions {
if assetPosition.AssetId != assetstypes.AssetUsdc.Id {
return false, errorsmod.Wrapf(
return false, false, errorsmod.Wrapf(
assetstypes.ErrNotImplementedMulticollateral,
"Asset %d is not supported",
assetPosition.AssetId,
Expand All @@ -257,7 +325,7 @@ func (c *Client) CheckSubaccountCollateralization(
for _, perpetualPosition := range settledSubaccount.PerpetualPositions {
perpetual, ok := perpetuals[perpetualPosition.PerpetualId]
if !ok {
return false, errorsmod.Wrapf(
return false, false, errorsmod.Wrapf(
perptypes.ErrPerpetualDoesNotExist,
"Perpetual not found for perpetual id %d",
perpetualPosition.PerpetualId,
Expand All @@ -266,7 +334,7 @@ func (c *Client) CheckSubaccountCollateralization(

marketPrice, ok := marketPrices[perpetual.Params.MarketId]
if !ok {
return false, errorsmod.Wrapf(
return false, false, errorsmod.Wrapf(
pricestypes.ErrMarketPriceDoesNotExist,
"MarketPrice not found for perpetual %+v",
perpetual,
Expand All @@ -281,7 +349,7 @@ func (c *Client) CheckSubaccountCollateralization(

liquidityTier, ok := liquidityTiers[perpetual.Params.LiquidityTier]
if !ok {
return false, errorsmod.Wrapf(
return false, false, errorsmod.Wrapf(
perptypes.ErrLiquidityTierDoesNotExist,
"LiquidityTier not found for perpetual %+v",
perpetual,
Expand All @@ -298,5 +366,7 @@ func (c *Client) CheckSubaccountCollateralization(
bigTotalMaintenanceMargin.Add(bigTotalMaintenanceMargin, bigMaintenanceMarginQuoteQuantums)
}

return clobkeeper.CanLiquidateSubaccount(bigTotalNetCollateral, bigTotalMaintenanceMargin), nil
return clobkeeper.CanLiquidateSubaccount(bigTotalNetCollateral, bigTotalMaintenanceMargin),
bigTotalNetCollateral.Sign() == -1,
nil
}
Loading
Loading