Skip to content

Commit

Permalink
[CLOB-1046] populate negative tnc subaccounts in grpc request (#890)
Browse files Browse the repository at this point in the history
* [CLOB-1046] populate negative tnc subaccounts in grpc request

* [CLOB-1047] populate subaccounts with open positions in grpc request (#892)

* [CLOB-1047] populate subaccounts with open positions in grpc request

* comments
  • Loading branch information
jayy04 authored Dec 18, 2023
1 parent 577500c commit 2294683
Show file tree
Hide file tree
Showing 10 changed files with 530 additions and 91 deletions.
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

0 comments on commit 2294683

Please sign in to comment.