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 1 commit
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
42 changes: 27 additions & 15 deletions protocol/x/clob/keeper/deleveraging.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package keeper
import (
"errors"
"fmt"
indexerevents "github.com/dydxprotocol/v4-chain/protocol/indexer/events"
"github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager"
"math/big"
"time"

indexerevents "github.com/dydxprotocol/v4-chain/protocol/indexer/events"
"github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager"

errorsmod "cosmossdk.io/errors"

gometrics "github.com/armon/go-metrics"
Expand Down Expand Up @@ -241,6 +242,7 @@ func (k Keeper) OffsetSubaccountPerpetualPosition(
*offsettingSubaccount.Id,
perpetualId,
deltaQuantums,
false,
jakob-dydx marked this conversation as resolved.
Show resolved Hide resolved
); err == nil {
// Update the remaining liquidatable quantums.
deltaQuantumsRemaining = new(big.Int).Sub(
Expand Down Expand Up @@ -323,6 +325,7 @@ func (k Keeper) ProcessDeleveraging(
offsettingSubaccountId satypes.SubaccountId,
perpetualId uint32,
deltaQuantums *big.Int,
fillPriceIsOraclePrice bool,
) (
err error,
) {
Expand Down Expand Up @@ -354,20 +357,29 @@ func (k Keeper) ProcessDeleveraging(
)
}

// 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
fillPriceDeltaQuoteQuantums := new(big.Int)
if fillPriceIsOraclePrice { // Flow used for final settlement deleveraging events
fillPriceDeltaQuoteQuantums, err = k.perpetualsKeeper.GetNetNotional(ctx, perpetualId, deltaQuantums)
fillPriceDeltaQuoteQuantums.Neg(fillPriceDeltaQuoteQuantums)
if err != nil {
return err
}
} else { // Regular deleveraging flow
// Calculate the bankruptcy price of the liquidated position. This is the price at which both positions
// are closed.
fillPriceDeltaQuoteQuantums, err = k.GetBankruptcyPriceInQuoteQuantums(
ctx,
liquidatedSubaccountId,
perpetualId,
deltaQuantums,
)
if err != nil {
return err
}
}

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

Expand Down Expand Up @@ -465,7 +477,7 @@ func (k Keeper) ProcessDeleveraging(
offsettingSubaccountId,
perpetualId,
satypes.BaseQuantums(new(big.Int).Abs(deltaQuantums).Uint64()),
satypes.BaseQuantums(bankruptcyPriceQuoteQuantums.Uint64()),
satypes.BaseQuantums(fillPriceDeltaQuoteQuantums.Uint64()),
deltaQuantums.Sign() > 0,
),
),
Expand Down
211 changes: 209 additions & 2 deletions protocol/x/clob/keeper/deleveraging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package keeper_test

import (
"errors"
indexerevents "github.com/dydxprotocol/v4-chain/protocol/indexer/events"
"github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager"
"math"
"math/big"
"testing"
"time"

indexerevents "github.com/dydxprotocol/v4-chain/protocol/indexer/events"
"github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager"

sdkmath "cosmossdk.io/math"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -1095,6 +1096,211 @@ func TestProcessDeleveraging(t *testing.T) {
*tc.offsettingSubaccount.GetId(),
uint32(0),
tc.deltaQuantums,
false,
)
if tc.expectedErr == nil {
require.NoError(t, err)

actualLiquidated := ks.SubaccountsKeeper.GetSubaccount(ks.Ctx, *tc.liquidatedSubaccount.GetId())
require.Equal(
t,
tc.expectedLiquidatedSubaccount,
actualLiquidated,
)

actualOffsetting := ks.SubaccountsKeeper.GetSubaccount(ks.Ctx, *tc.offsettingSubaccount.GetId())
require.Equal(
t,
tc.expectedOffsettingSubaccount,
actualOffsetting,
)
} else {
require.ErrorContains(t, err, tc.expectedErr.Error())
}
})
}
}

func TestProcessDeleveragingAtOraclePrice(t *testing.T) {
tests := map[string]struct {
// Setup.
liquidatedSubaccount satypes.Subaccount
jakob-dydx marked this conversation as resolved.
Show resolved Hide resolved
offsettingSubaccount satypes.Subaccount
deltaQuantums *big.Int

// Expectations.
expectedLiquidatedSubaccount satypes.Subaccount
jakob-dydx marked this conversation as resolved.
Show resolved Hide resolved
expectedOffsettingSubaccount satypes.Subaccount
expectedErr error
}{
"Liquidated: well-collateralized, offsetting: well-collateralized": {
jakob-dydx marked this conversation as resolved.
Show resolved Hide resolved
liquidatedSubaccount: constants.Carl_Num0_1BTC_Short_100000USD,
offsettingSubaccount: constants.Dave_Num0_1BTC_Long_50000USD,
deltaQuantums: big.NewInt(100_000_000), // 1 BTC

expectedLiquidatedSubaccount: satypes.Subaccount{
Id: &constants.Carl_Num0,
AssetPositions: keepertest.CreateUsdcAssetPosition(
big.NewInt(100_000_000_000 - 50_000_000_000),
),
},
expectedOffsettingSubaccount: satypes.Subaccount{
Id: &constants.Dave_Num0,
AssetPositions: keepertest.CreateUsdcAssetPosition(
big.NewInt(50_000_000_000 + 50_000_000_000),
),
},
},
"Liquidated: well-collateralized, offsetting: under-collateralized, TNC > 0": {
liquidatedSubaccount: constants.Dave_Num0_1BTC_Long_50000USD,
offsettingSubaccount: constants.Carl_Num0_1BTC_Short_54999USD,
deltaQuantums: big.NewInt(-100_000_000), // 1 BTC

expectedLiquidatedSubaccount: satypes.Subaccount{
Id: &constants.Dave_Num0,
AssetPositions: keepertest.CreateUsdcAssetPosition(
big.NewInt(50_000_000_000 + 50_000_000_000),
),
},
expectedOffsettingSubaccount: satypes.Subaccount{
Id: &constants.Carl_Num0,
AssetPositions: keepertest.CreateUsdcAssetPosition(
big.NewInt(54_999_000_000 - 50_000_000_000),
),
},
},
"Liquidated: well-collateralized, offsetting: under-collateralized, TNC == 0": {
liquidatedSubaccount: constants.Carl_Num0_1BTC_Short_100000USD,
offsettingSubaccount: constants.Dave_Num0_1BTC_Long_50000USD_Short,
deltaQuantums: big.NewInt(100_000_000), // 1 BTC

expectedLiquidatedSubaccount: satypes.Subaccount{
Id: &constants.Carl_Num0,
AssetPositions: keepertest.CreateUsdcAssetPosition(
big.NewInt(100_000_000_000 - 50_000_000_000),
),
},
expectedOffsettingSubaccount: satypes.Subaccount{
Id: &constants.Dave_Num0,
},
},
"Liquidated: well-collateralized, offsetting: under-collateralized, TNC < 0": {
liquidatedSubaccount: constants.Carl_Num0_1BTC_Short_100000USD,
offsettingSubaccount: constants.Dave_Num0_1BTC_Long_50001USD_Short,
deltaQuantums: big.NewInt(100_000_000), // 1 BTC

// Negative TNC account closing at oracle price is an invalid state transition.
expectedErr: satypes.ErrFailedToUpdateSubaccounts,
},
"Liquidated: under-collateralized, TNC > 0, offsetting: well-collateralized": {
liquidatedSubaccount: constants.Carl_Num0_1BTC_Short_54999USD,
offsettingSubaccount: constants.Dave_Num0_1BTC_Long_50000USD,
deltaQuantums: big.NewInt(100_000_000), // 1 BTC

expectedLiquidatedSubaccount: satypes.Subaccount{
Id: &constants.Carl_Num0,
AssetPositions: keepertest.CreateUsdcAssetPosition(
big.NewInt(54_999_000_000 - 50_000_000_000),
),
},
expectedOffsettingSubaccount: satypes.Subaccount{
Id: &constants.Dave_Num0,
AssetPositions: keepertest.CreateUsdcAssetPosition(
big.NewInt(50_000_000_000 + 50_000_000_000),
),
},
},
"Liquidated: under-collateralized, TNC == 0, offsetting: under-collateralized, TNC < 0": {
liquidatedSubaccount: constants.Carl_Num0_1BTC_Short_50000USD,
offsettingSubaccount: constants.Dave_Num0_1BTC_Long_50001USD_Short,
deltaQuantums: big.NewInt(100_000_000), // 1 BTC

// Negative TNC account closing at oracle price is an invalid state transition.
expectedErr: satypes.ErrFailedToUpdateSubaccounts,
},
"Liquidated: under-collateralized, TNC < 0, offsetting: under-collateralized, TNC > 0": {
liquidatedSubaccount: constants.Carl_Num0_1BTC_Short_49999USD,
offsettingSubaccount: constants.Dave_Num0_1BTC_Long_45001USD_Short,
deltaQuantums: big.NewInt(100_000_000), // 1 BTC

// Negative TNC account closing at oracle price is an invalid state transition.
expectedErr: satypes.ErrFailedToUpdateSubaccounts,
},
"Liquidated: under-collateralized, TNC < 0, offsetting: well-collateralized": {
liquidatedSubaccount: constants.Carl_Num0_1BTC_Short_49999USD,
offsettingSubaccount: constants.Dave_Num0_1BTC_Long_50000USD,
deltaQuantums: big.NewInt(100_000_000), // 1 BTC

// Negative TNC account closing at oracle price is an invalid state transition.
expectedErr: satypes.ErrFailedToUpdateSubaccounts,
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
memClob := memclob.NewMemClobPriceTimePriority(false)
mockIndexerEventManager := &mocks.IndexerEventManager{}
ks := keepertest.NewClobKeepersTestContext(t, memClob, &mocks.BankKeeper{}, mockIndexerEventManager)

// Create the default markets.
keepertest.CreateTestMarkets(t, ks.Ctx, ks.PricesKeeper)

// Create liquidity tiers.
keepertest.CreateTestLiquidityTiers(t, ks.Ctx, ks.PerpetualsKeeper)

err := keepertest.CreateUsdcAsset(ks.Ctx, ks.AssetsKeeper)
require.NoError(t, err)

for _, p := range []perptypes.Perpetual{
constants.BtcUsd_20PercentInitial_10PercentMaintenance,
constants.EthUsd_20PercentInitial_10PercentMaintenance,
} {
_, err := ks.PerpetualsKeeper.CreatePerpetual(
ks.Ctx,
p.Params.Id,
p.Params.Ticker,
p.Params.MarketId,
p.Params.AtomicResolution,
p.Params.DefaultFundingPpm,
p.Params.LiquidityTier,
)
require.NoError(t, err)
}

ks.SubaccountsKeeper.SetSubaccount(ks.Ctx, tc.liquidatedSubaccount)
ks.SubaccountsKeeper.SetSubaccount(ks.Ctx, tc.offsettingSubaccount)

if tc.expectedErr == nil {
fillPriceQuoteQuantums, err := ks.PerpetualsKeeper.GetNetNotional(
ks.Ctx,
uint32(0),
tc.deltaQuantums,
)
fillPriceQuoteQuantums.Neg(fillPriceQuoteQuantums)
require.NoError(t, err)
mockIndexerEventManager.On("AddTxnEvent",
ks.Ctx,
indexerevents.SubtypeDeleveraging,
indexerevents.DeleveragingEventVersion,
indexer_manager.GetBytes(
indexerevents.NewDeleveragingEvent(
*tc.liquidatedSubaccount.GetId(),
*tc.offsettingSubaccount.GetId(),
uint32(0),
satypes.BaseQuantums(new(big.Int).Abs(tc.deltaQuantums).Uint64()),
satypes.BaseQuantums(fillPriceQuoteQuantums.Uint64()),
tc.deltaQuantums.Sign() > 0,
),
),
).Return()
}
err = ks.ClobKeeper.ProcessDeleveraging(
ks.Ctx,
*tc.liquidatedSubaccount.GetId(),
*tc.offsettingSubaccount.GetId(),
uint32(0),
tc.deltaQuantums,
true,
)
if tc.expectedErr == nil {
require.NoError(t, err)
Expand Down Expand Up @@ -1252,6 +1458,7 @@ func TestProcessDeleveraging_Rounding(t *testing.T) {
*tc.offsettingSubaccount.GetId(),
uint32(0),
tc.deltaQuantums,
false,
)
if tc.expectedErr == nil {
require.NoError(t, err)
Expand Down
1 change: 1 addition & 0 deletions protocol/x/clob/keeper/process_operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,7 @@ func (k Keeper) PersistMatchDeleveragingToState(
fill.OffsettingSubaccountId,
perpetualId,
deltaQuantums,
false,
jakob-dydx marked this conversation as resolved.
Show resolved Hide resolved
); err != nil {
return errorsmod.Wrapf(
types.ErrInvalidDeleveragingFill,
Expand Down
Loading