Skip to content

Commit

Permalink
[Performance] Optimize state-reads of perpetual information when doin…
Browse files Browse the repository at this point in the history
…g collateral checks (#1691)
  • Loading branch information
BrendanChou authored Jun 14, 2024
1 parent 8f7c328 commit cafe79f
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 181 deletions.
104 changes: 50 additions & 54 deletions protocol/daemons/liquidation/client/sub_task_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@ func (s *SubTaskRunnerImpl) RunLiquidationDaemonTaskLoop(

// 1. Fetch all information needed to calculate total net collateral and margin requirements.
subaccounts,
marketPrices,
perpetuals,
liquidityTiers,
perpInfos,
err := daemonClient.FetchApplicationStateAtBlockHeight(
ctx,
lastCommittedBlockHeight,
Expand All @@ -73,9 +71,7 @@ func (s *SubTaskRunnerImpl) RunLiquidationDaemonTaskLoop(
negativeTncSubaccountIds,
err := daemonClient.GetLiquidatableSubaccountIds(
subaccounts,
marketPrices,
perpetuals,
liquidityTiers,
perpInfos,
)
if err != nil {
return err
Expand Down Expand Up @@ -111,9 +107,7 @@ func (c *Client) FetchApplicationStateAtBlockHeight(
liqFlags flags.LiquidationFlags,
) (
subaccounts []satypes.Subaccount,
marketPricesMap map[uint32]pricestypes.MarketPrice,
perpetualsMap map[uint32]perptypes.Perpetual,
liquidityTiersMap map[uint32]perptypes.LiquidityTier,
perpInfos map[uint32]perptypes.PerpInfo,
err error,
) {
defer telemetry.ModuleMeasureSince(
Expand All @@ -129,46 +123,66 @@ func (c *Client) FetchApplicationStateAtBlockHeight(
// Subaccounts
subaccounts, err = c.GetAllSubaccounts(queryCtx, liqFlags.QueryPageLimit)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, err
}

// Market prices
marketPrices, err := c.GetAllMarketPrices(queryCtx, liqFlags.QueryPageLimit)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, err
}
marketPricesMap = lib.UniqueSliceToMap(marketPrices, func(m pricestypes.MarketPrice) uint32 {
marketPricesMap := lib.UniqueSliceToMap(marketPrices, func(m pricestypes.MarketPrice) uint32 {
return m.Id
})

// Perpetuals
perpetuals, err := c.GetAllPerpetuals(queryCtx, liqFlags.QueryPageLimit)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, err
}
perpetualsMap = lib.UniqueSliceToMap(perpetuals, func(p perptypes.Perpetual) uint32 {
return p.Params.Id
})

// Liquidity tiers
liquidityTiers, err := c.GetAllLiquidityTiers(queryCtx, liqFlags.QueryPageLimit)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, err
}
liquidityTiersMap = lib.UniqueSliceToMap(liquidityTiers, func(l perptypes.LiquidityTier) uint32 {
liquidityTiersMap := lib.UniqueSliceToMap(liquidityTiers, func(l perptypes.LiquidityTier) uint32 {
return l.Id
})

return subaccounts, marketPricesMap, perpetualsMap, liquidityTiersMap, nil
perpInfos = make(map[uint32]perptypes.PerpInfo, len(perpetuals))
for _, perp := range perpetuals {
price, ok := marketPricesMap[perp.Params.MarketId]
if !ok {
return nil, nil, errorsmod.Wrapf(
pricestypes.ErrMarketPriceDoesNotExist,
"%d",
perp.Params.MarketId,
)
}
liquidityTier, ok := liquidityTiersMap[perp.Params.LiquidityTier]
if !ok {
return nil, nil, errorsmod.Wrapf(
perptypes.ErrLiquidityTierDoesNotExist,
"%d",
perp.Params.LiquidityTier,
)
}
perpInfos[perp.Params.Id] = perptypes.PerpInfo{
Perpetual: perp,
Price: price,
LiquidityTier: liquidityTier,
}
}

return subaccounts, perpInfos, nil
}

// GetLiquidatableSubaccountIds verifies collateralization statuses of subaccounts with
// at least one open position and returns a list of unique and potentially liquidatable subaccount ids.
func (c *Client) GetLiquidatableSubaccountIds(
subaccounts []satypes.Subaccount,
marketPrices map[uint32]pricestypes.MarketPrice,
perpetuals map[uint32]perptypes.Perpetual,
liquidityTiers map[uint32]perptypes.LiquidityTier,
perpInfos map[uint32]perptypes.PerpInfo,
) (
liquidatableSubaccountIds []satypes.SubaccountId,
negativeTncSubaccountIds []satypes.SubaccountId,
Expand All @@ -192,9 +206,7 @@ func (c *Client) GetLiquidatableSubaccountIds(
// Check if the subaccount is liquidatable.
isLiquidatable, hasNegativeTnc, err := c.CheckSubaccountCollateralization(
subaccount,
marketPrices,
perpetuals,
liquidityTiers,
perpInfos,
)
if err != nil {
c.logger.Error("Error checking collateralization status", "error", err)
Expand Down Expand Up @@ -278,9 +290,7 @@ func (c *Client) GetSubaccountOpenPositionInfo(
// is not yet implemented.
func (c *Client) CheckSubaccountCollateralization(
unsettledSubaccount satypes.Subaccount,
marketPrices map[uint32]pricestypes.MarketPrice,
perpetuals map[uint32]perptypes.Perpetual,
liquidityTiers map[uint32]perptypes.LiquidityTier,
perpInfos map[uint32]perptypes.PerpInfo,
) (
isLiquidatable bool,
hasNegativeTnc bool,
Expand All @@ -297,7 +307,7 @@ func (c *Client) CheckSubaccountCollateralization(
// to ensure that the funding payments are included in the net collateral calculation.
settledSubaccount, _, err := sakeeper.GetSettledSubaccountWithPerpetuals(
unsettledSubaccount,
perpetuals,
perpInfos,
)
if err != nil {
return false, false, err
Expand All @@ -323,44 +333,30 @@ func (c *Client) CheckSubaccountCollateralization(

// Calculate the net collateral and maintenance margin for each of the perpetual positions.
for _, perpetualPosition := range settledSubaccount.PerpetualPositions {
perpetual, ok := perpetuals[perpetualPosition.PerpetualId]
perpInfo, ok := perpInfos[perpetualPosition.PerpetualId]
if !ok {
return false, false, errorsmod.Wrapf(
perptypes.ErrPerpetualDoesNotExist,
"Perpetual not found for perpetual id %d",
satypes.ErrPerpetualInfoDoesNotExist,
"%d",
perpetualPosition.PerpetualId,
)
}

marketPrice, ok := marketPrices[perpetual.Params.MarketId]
if !ok {
return false, false, errorsmod.Wrapf(
pricestypes.ErrMarketPriceDoesNotExist,
"MarketPrice not found for perpetual %+v",
perpetual,
)
}

bigQuantums := perpetualPosition.GetBigQuantums()

// Get the net collateral for the position.
bigNetCollateralQuoteQuantums := perplib.GetNetNotionalInQuoteQuantums(perpetual, marketPrice, bigQuantums)
bigNetCollateralQuoteQuantums := perplib.GetNetNotionalInQuoteQuantums(
perpInfo.Perpetual,
perpInfo.Price,
bigQuantums,
)
bigTotalNetCollateral.Add(bigTotalNetCollateral, bigNetCollateralQuoteQuantums)

liquidityTier, ok := liquidityTiers[perpetual.Params.LiquidityTier]
if !ok {
return false, false, errorsmod.Wrapf(
perptypes.ErrLiquidityTierDoesNotExist,
"LiquidityTier not found for perpetual %+v",
perpetual,
)
}

// Get the maintenance margin requirement for the position.
_, bigMaintenanceMarginQuoteQuantums := perplib.GetMarginRequirementsInQuoteQuantums(
perpetual,
marketPrice,
liquidityTier,
perpInfo.Perpetual,
perpInfo.Price,
perpInfo.LiquidityTier,
bigQuantums,
)
bigTotalMaintenanceMargin.Add(bigTotalMaintenanceMargin, bigMaintenanceMarginQuoteQuantums)
Expand Down
12 changes: 12 additions & 0 deletions protocol/x/perpetuals/types/perpinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package types

import (
pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types"
)

// PerpInfo contains all information needed to calculate margin requirements for a perpetual.
type PerpInfo struct {
Perpetual Perpetual
Price pricestypes.MarketPrice
LiquidityTier LiquidityTier
}
53 changes: 17 additions & 36 deletions protocol/x/subaccounts/keeper/isolated_subaccount.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/dydxprotocol/v4-chain/protocol/lib"
assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types"
perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types"
"github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types"
Expand All @@ -25,18 +24,17 @@ import (
func (k Keeper) checkIsolatedSubaccountConstraints(
ctx sdk.Context,
settledUpdates []SettledUpdate,
perpetuals []perptypes.Perpetual,
perpInfos map[uint32]perptypes.PerpInfo,
) (
success bool,
successPerUpdate []types.UpdateResult,
err error,
) {
success = true
successPerUpdate = make([]types.UpdateResult, len(settledUpdates))
perpIdToMarketType := getPerpIdToMarketTypeMap(perpetuals)

for i, u := range settledUpdates {
result, err := isValidIsolatedPerpetualUpdates(u, perpIdToMarketType)
result, err := isValidIsolatedPerpetualUpdates(u, perpInfos)
if err != nil {
return false, nil, err
}
Expand All @@ -62,7 +60,7 @@ func (k Keeper) checkIsolatedSubaccountConstraints(
// perpetuals or a combination of isolated and non-isolated perpetuals
func isValidIsolatedPerpetualUpdates(
settledUpdate SettledUpdate,
perpIdToMarketType map[uint32]perptypes.PerpetualMarketType,
perpInfos map[uint32]perptypes.PerpInfo,
) (types.UpdateResult, error) {
// If there are no perpetual updates, then this update does not violate constraints for isolated
// markets.
Expand All @@ -74,14 +72,13 @@ func isValidIsolatedPerpetualUpdates(
hasIsolatedUpdate := false
isolatedUpdatePerpetualId := uint32(math.MaxUint32)
for _, perpetualUpdate := range settledUpdate.PerpetualUpdates {
marketType, exists := perpIdToMarketType[perpetualUpdate.PerpetualId]
perpInfo, exists := perpInfos[perpetualUpdate.PerpetualId]
if !exists {
return types.UpdateCausedError, errorsmod.Wrap(
perptypes.ErrPerpetualDoesNotExist, lib.UintToString(perpetualUpdate.PerpetualId),
)
return types.UpdateCausedError,
errorsmod.Wrapf(types.ErrPerpetualInfoDoesNotExist, "%d", perpetualUpdate.PerpetualId)
}

if marketType == perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED {
if perpInfo.Perpetual.Params.MarketType == perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED {
hasIsolatedUpdate = true
isolatedUpdatePerpetualId = perpetualUpdate.PerpetualId
break
Expand All @@ -94,14 +91,13 @@ func isValidIsolatedPerpetualUpdates(
isolatedPositionPerpetualId := uint32(math.MaxUint32)
hasPerpetualPositions := len(settledUpdate.SettledSubaccount.PerpetualPositions) > 0
for _, perpetualPosition := range settledUpdate.SettledSubaccount.PerpetualPositions {
marketType, exists := perpIdToMarketType[perpetualPosition.PerpetualId]
perpInfo, exists := perpInfos[perpetualPosition.PerpetualId]
if !exists {
return types.UpdateCausedError, errorsmod.Wrap(
perptypes.ErrPerpetualDoesNotExist, lib.UintToString(perpetualPosition.PerpetualId),
)
return types.UpdateCausedError,
errorsmod.Wrapf(types.ErrPerpetualInfoDoesNotExist, "%d", perpetualPosition.PerpetualId)
}

if marketType == perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED {
if perpInfo.Perpetual.Params.MarketType == perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED {
isIsolatedSubaccount = true
isolatedPositionPerpetualId = perpetualPosition.PerpetualId
break
Expand Down Expand Up @@ -146,9 +142,8 @@ func isValidIsolatedPerpetualUpdates(
// so all the updates must have been applied already to the subaccount.
func GetIsolatedPerpetualStateTransition(
settledUpdateWithUpdatedSubaccount SettledUpdate,
perpetuals []perptypes.Perpetual,
perpInfos map[uint32]perptypes.PerpInfo,
) (*types.IsolatedPerpetualPositionStateTransition, error) {
perpIdToMarketType := getPerpIdToMarketTypeMap(perpetuals)
// This subaccount needs to have had the updates in the `settledUpdate` already applied to it.
updatedSubaccount := settledUpdateWithUpdatedSubaccount.SettledSubaccount
// If there are no perpetual updates, then no perpetual position could have been opened or closed
Expand All @@ -168,15 +163,13 @@ func GetIsolatedPerpetualStateTransition(
// Now, from the above checks, we know there is only a single perpetual update and 0 or 1 perpetual
// positions.
perpetualUpdate := settledUpdateWithUpdatedSubaccount.PerpetualUpdates[0]
marketType, exists := perpIdToMarketType[perpetualUpdate.PerpetualId]
perpInfo, exists := perpInfos[perpetualUpdate.PerpetualId]
if !exists {
return nil, errorsmod.Wrap(
perptypes.ErrPerpetualDoesNotExist, lib.UintToString(perpetualUpdate.PerpetualId),
)
return nil, errorsmod.Wrapf(types.ErrPerpetualInfoDoesNotExist, "%d", perpetualUpdate.PerpetualId)
}
// If the perpetual update is not for an isolated perpetual, no isolated perpetual position is
// being opened or closed.
if marketType != perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED {
if perpInfo.Perpetual.Params.MarketType != perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED {
return nil, nil
}

Expand Down Expand Up @@ -325,13 +318,13 @@ func (k *Keeper) transferCollateralForIsolatedPerpetual(
func (k *Keeper) computeAndExecuteCollateralTransfer(
ctx sdk.Context,
settledUpdateWithUpdatedSubaccount SettledUpdate,
perpetuals []perptypes.Perpetual,
perpInfos map[uint32]perptypes.PerpInfo,
) error {
// The subaccount in `settledUpdateWithUpdatedSubaccount` already has the perpetual updates
// and asset updates applied to it.
stateTransition, err := GetIsolatedPerpetualStateTransition(
settledUpdateWithUpdatedSubaccount,
perpetuals,
perpInfos,
)
if err != nil {
return err
Expand All @@ -345,15 +338,3 @@ func (k *Keeper) computeAndExecuteCollateralTransfer(

return nil
}

func getPerpIdToMarketTypeMap(
perpetuals []perptypes.Perpetual,
) map[uint32]perptypes.PerpetualMarketType {
var perpIdToMarketType = make(map[uint32]perptypes.PerpetualMarketType, len(perpetuals))

for _, perpetual := range perpetuals {
perpIdToMarketType[perpetual.GetId()] = perpetual.Params.MarketType
}

return perpIdToMarketType
}
9 changes: 8 additions & 1 deletion protocol/x/subaccounts/keeper/isolated_subaccount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,16 @@ func TestGetIsolatedPerpetualStateTransition(t *testing.T) {
for name, tc := range tests {
t.Run(
name, func(t *testing.T) {
perpInfos := make(map[uint32]perptypes.PerpInfo, len(tc.perpetuals))
for _, perp := range tc.perpetuals {
perpInfos[perp.Params.Id] = perptypes.PerpInfo{
Perpetual: perp,
}
}

stateTransition, err := keeper.GetIsolatedPerpetualStateTransition(
tc.settledUpdateWithUpdatedSubaccount,
tc.perpetuals,
perpInfos,
)
if tc.expectedErr != nil {
require.Error(t, tc.expectedErr, err)
Expand Down
Loading

0 comments on commit cafe79f

Please sign in to comment.