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-1028] allow settlement at oracle price in ProcessDeleveraging #835

Merged
merged 7 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
93 changes: 65 additions & 28 deletions protocol/x/clob/keeper/deleveraging.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,13 +236,36 @@ func (k Keeper) OffsetSubaccountPerpetualPosition(
deltaQuantums = new(big.Int).Set(deltaQuantumsRemaining)
}

// Fetch delta quote quantums. Calculated at bankruptcy price for standard
// deleveraging and at oracle price for final settlement deleveraging.
deltaQuoteQuantums, err := k.getDeleveragingQuoteQuantumsDelta(
ctx,
perpetualId,
liquidatedSubaccountId,
deltaQuantums,
)
Comment on lines +241 to +246
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errors returned from bankruptcy price calculation were previously caught within ProcessDeleveraging and recovered from in OffsetSubaccountPerpetualPosition, continuing onto the next iteration without breaking. I am opting to continue this pattern, but will log a debug msg

if err != nil {
liquidatedSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, liquidatedSubaccountId)
k.Logger(ctx).Error(
"Encountered error when getting quote quantums for deleveraging",
"error", err,
"blockHeight", ctx.BlockHeight(),
"perpetualId", perpetualId,
"deltaQuantums", deltaQuantums,
"liquidatedSubaccount", liquidatedSubaccount,
"offsettingSubaccount", offsettingSubaccount,
)
return false
}

// Try to process the deleveraging operation for both subaccounts.
if err := k.ProcessDeleveraging(
ctx,
liquidatedSubaccountId,
*offsettingSubaccount.Id,
perpetualId,
deltaQuantums,
deltaQuoteQuantums,
); err == nil {
// Update the remaining liquidatable quantums.
deltaQuantumsRemaining = new(big.Int).Sub(
Expand Down Expand Up @@ -305,22 +328,48 @@ func (k Keeper) OffsetSubaccountPerpetualPosition(
return fills, deltaQuantumsRemaining
}

// getDeleveragingQuoteQuantums returns the quote quantums delta to apply to a deleveraging operation.
// This returns the bankruptcy price for standard deleveraging operations, and the oracle price for
// final settlement deleveraging operations. The type of deleveraging event is determined by the
// clob pair status of the clob pair associated with the provided perpetual.
func (k Keeper) getDeleveragingQuoteQuantumsDelta(
ctx sdk.Context,
perpetualId uint32,
subaccountId satypes.SubaccountId,
deltaQuantums *big.Int,
) (*big.Int, error) {
clobPair := k.mustGetClobPairForPerpetualId(ctx, perpetualId)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: is it okay if we panic here? This logic is executed in PrepareCheckState. Would we rather recover from this and continue? I think it is fine to panic but just want to double check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah should be fine to panic - if clob pair is missing, then something is really really wrong

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized this function isn't doing exactly what I want for usability. The purpose of this function is to allow the deleveraging flow to be reusable such that non-negative TNC subaccounts with positions in a final settlement market can be closed at the oracle price.

Currently this function is written such that ALL subaccounts (including negative TNC) for the closed market will be settled at oracle price. This isn't what we want. I think the easiest way to updated this would be to do a collateral check and use the oracle price iff isFinalSettlement && nonNegativeTNC.

I think what I'll do here is use CanDeleverageSubaccount to determine if the account has non negative TNC and only call this if the market is in final settlement.

isFinalSettlement := clobPair.Status == types.ClobPair_STATUS_FINAL_SETTLEMENT

if isFinalSettlement {
return k.perpetualsKeeper.GetNetNotional(ctx, perpetualId, deltaQuantums)
}

return k.GetBankruptcyPriceInQuoteQuantums(
ctx,
subaccountId,
perpetualId,
deltaQuantums,
)
}

// ProcessDeleveraging processes a deleveraging operation by closing both the liquidated subaccount's
// position and the offsetting subaccount's position at the bankruptcy price of the _liquidated_ position.
// This function takes a `deltaQuantums` argument, which is the delta with respect to the liquidated subaccount's
// position, to allow for partial deleveraging. This function emits a cometbft event if the deleveraging match
// is successfully written to state.
//
// This function returns an error if:
// - `deltaQuantums` is not valid with respect to either of the subaccounts.
// - `deltaBaseQuantums` is not valid with respect to either of the subaccounts.
// - `GetBankruptcyPriceInQuoteQuantums` returns an error.
// - subaccount updates cannot be applied when the bankruptcy prices of both subaccounts don't overlap.
func (k Keeper) ProcessDeleveraging(
ctx sdk.Context,
liquidatedSubaccountId satypes.SubaccountId,
offsettingSubaccountId satypes.SubaccountId,
perpetualId uint32,
deltaQuantums *big.Int,
deltaBaseQuantums *big.Int,
deltaQuoteQuantums *big.Int,
) (
err error,
) {
Expand All @@ -338,36 +387,24 @@ func (k Keeper) ProcessDeleveraging(
// by checking that `deltaQuantums` is on the opposite side of the liquidated position side,
// the same side as the offsetting subaccount position side, and the magnitude of `deltaQuantums`
// is not larger than both positions.
if liquidatedPositionQuantums.Sign()*deltaQuantums.Sign() != -1 ||
liquidatedPositionQuantums.CmpAbs(deltaQuantums) == -1 ||
offsettingPositionQuantums.Sign()*deltaQuantums.Sign() != 1 ||
offsettingPositionQuantums.CmpAbs(deltaQuantums) == -1 {
if liquidatedPositionQuantums.Sign()*deltaBaseQuantums.Sign() != -1 ||
liquidatedPositionQuantums.CmpAbs(deltaBaseQuantums) == -1 ||
offsettingPositionQuantums.Sign()*deltaBaseQuantums.Sign() != 1 ||
offsettingPositionQuantums.CmpAbs(deltaBaseQuantums) == -1 {
return errorsmod.Wrapf(
types.ErrInvalidPerpetualPositionSizeDelta,
"ProcessDeleveraging: liquidated = (%s), offsetting = (%s), perpetual id = (%d), deltaQuantums = (%+v)",
lib.MaybeGetJsonString(liquidatedSubaccount),
lib.MaybeGetJsonString(offsettingSubaccount),
perpetualId,
deltaQuantums,
deltaBaseQuantums,
)
}

// Calculate the bankruptcy price of the liquidated position. This is the price at which both positions
// are closed.
bankruptcyPriceQuoteQuantums, err := k.GetBankruptcyPriceInQuoteQuantums(
ctx,
liquidatedSubaccountId,
perpetualId,
deltaQuantums,
)
if err != nil {
return err
}

deleveragedSubaccountQuoteBalanceDelta := bankruptcyPriceQuoteQuantums
offsettingSubaccountQuoteBalanceDelta := new(big.Int).Neg(bankruptcyPriceQuoteQuantums)
deleveragedSubaccountPerpetualQuantumsDelta := deltaQuantums
offsettingSubaccountPerpetualQuantumsDelta := new(big.Int).Neg(deltaQuantums)
deleveragedSubaccountQuoteBalanceDelta := deltaQuoteQuantums
offsettingSubaccountQuoteBalanceDelta := new(big.Int).Neg(deltaQuoteQuantums)
deleveragedSubaccountPerpetualQuantumsDelta := deltaBaseQuantums
offsettingSubaccountPerpetualQuantumsDelta := new(big.Int).Neg(deltaBaseQuantums)

updates := []satypes.Update{
// Liquidated subaccount update.
Expand Down Expand Up @@ -419,12 +456,12 @@ func (k Keeper) ProcessDeleveraging(
if deleveragedQuoteQuantums, err := k.perpetualsKeeper.GetNetCollateral(
ctx,
perpetualId,
new(big.Int).Abs(deltaQuantums),
new(big.Int).Abs(deltaBaseQuantums),
); err == nil {
labels := []metrics.Label{
metrics.GetLabelForIntValue(metrics.PerpetualId, int(perpetualId)),
metrics.GetLabelForBoolValue(metrics.CheckTx, ctx.IsCheckTx()),
metrics.GetLabelForBoolValue(metrics.IsLong, deltaQuantums.Sign() == -1),
metrics.GetLabelForBoolValue(metrics.IsLong, deltaBaseQuantums.Sign() == -1),
}

metrics.AddSampleWithLabels(
Expand Down Expand Up @@ -463,9 +500,9 @@ func (k Keeper) ProcessDeleveraging(
liquidatedSubaccountId,
offsettingSubaccountId,
perpetualId,
satypes.BaseQuantums(new(big.Int).Abs(deltaQuantums).Uint64()),
satypes.BaseQuantums(bankruptcyPriceQuoteQuantums.Uint64()),
deltaQuantums.Sign() > 0,
satypes.BaseQuantums(new(big.Int).Abs(deltaBaseQuantums).Uint64()),
satypes.BaseQuantums(deltaQuoteQuantums.Uint64()),
deltaBaseQuantums.Sign() > 0,
),
),
)
Expand Down
Loading
Loading