From d5d8f3827082d69368f6d4f96869974ce0df7407 Mon Sep 17 00:00:00 2001 From: Teddy Ding Date: Tue, 19 Mar 2024 15:40:48 -0400 Subject: [PATCH 1/8] use OIMF to evaluate end state; need to fix e2e test --- protocol/mocks/PerpetualsKeeper.go | 18 + protocol/testutil/constants/perpetuals.go | 40 ++ protocol/testutil/perpetuals/perpetuals.go | 29 + protocol/x/clob/keeper/deleveraging_test.go | 44 +- protocol/x/clob/keeper/liquidations_test.go | 43 +- protocol/x/clob/keeper/orders.go | 2 +- protocol/x/clob/keeper/orders_test.go | 15 + protocol/x/perpetuals/keeper/perpetual.go | 3 +- protocol/x/perpetuals/types/types.go | 14 + protocol/x/subaccounts/keeper/oimf.go | 89 ++++ protocol/x/subaccounts/keeper/oimf_test.go | 302 +++++++++++ protocol/x/subaccounts/keeper/subaccount.go | 60 ++- .../x/subaccounts/keeper/subaccount_test.go | 494 +++++++++++++++++- protocol/x/subaccounts/types/errors.go | 26 +- .../x/subaccounts/types/expected_keepers.go | 1 + protocol/x/subaccounts/types/update.go | 14 +- 16 files changed, 1157 insertions(+), 37 deletions(-) create mode 100644 protocol/x/subaccounts/keeper/oimf.go create mode 100644 protocol/x/subaccounts/keeper/oimf_test.go diff --git a/protocol/mocks/PerpetualsKeeper.go b/protocol/mocks/PerpetualsKeeper.go index 515ba331ad..85da4a3b2b 100644 --- a/protocol/mocks/PerpetualsKeeper.go +++ b/protocol/mocks/PerpetualsKeeper.go @@ -259,6 +259,24 @@ func (_m *PerpetualsKeeper) MaybeProcessNewFundingTickEpoch(ctx types.Context) { _m.Called(ctx) } +// ModifyOpenInterest provides a mock function with given fields: ctx, perpetualId, openInterestDeltaBaseQuantums +func (_m *PerpetualsKeeper) ModifyOpenInterest(ctx types.Context, perpetualId uint32, openInterestDeltaBaseQuantums *big.Int) error { + ret := _m.Called(ctx, perpetualId, openInterestDeltaBaseQuantums) + + if len(ret) == 0 { + panic("no return value specified for ModifyOpenInterest") + } + + var r0 error + if rf, ok := ret.Get(0).(func(types.Context, uint32, *big.Int) error); ok { + r0 = rf(ctx, perpetualId, openInterestDeltaBaseQuantums) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // ModifyPerpetual provides a mock function with given fields: ctx, id, ticker, marketId, defaultFundingPpm, liquidityTier func (_m *PerpetualsKeeper) ModifyPerpetual(ctx types.Context, id uint32, ticker string, marketId uint32, defaultFundingPpm int32, liquidityTier uint32) (perpetualstypes.Perpetual, error) { ret := _m.Called(ctx, id, ticker, marketId, defaultFundingPpm, liquidityTier) diff --git a/protocol/testutil/constants/perpetuals.go b/protocol/testutil/constants/perpetuals.go index a27535afe1..1d4a9f06d3 100644 --- a/protocol/testutil/constants/perpetuals.go +++ b/protocol/testutil/constants/perpetuals.go @@ -1,6 +1,8 @@ package constants import ( + "math/big" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" ) @@ -81,6 +83,15 @@ var LiquidityTiers = []perptypes.LiquidityTier{ MaintenanceFractionPpm: 1_000_000, ImpactNotional: 50_454_000_000, }, + { + Id: 9, + Name: "9", + InitialMarginPpm: 200_000, // 20% + MaintenanceFractionPpm: 500_000, // 20% * 0.5 = 10% + ImpactNotional: 2_500_000_000, + OpenInterestUpperCap: 50_000_000_000_000, // 50mm USDC + OpenInterestLowerCap: 25_000_000_000_000, // 25mm USDC + }, { Id: 101, Name: "101", @@ -90,6 +101,22 @@ var LiquidityTiers = []perptypes.LiquidityTier{ }, } +// Perpetual OI setup in tests +var ( + BtcUsd_OpenInterest1_AtomicRes8 = perptypes.OpenInterestDelta{ + PerpetualId: 0, + BaseQuantumsDelta: big.NewInt(100_000_000), + } + EthUsd_OpenInterest1_AtomicRes9 = perptypes.OpenInterestDelta{ + PerpetualId: 1, + BaseQuantumsDelta: big.NewInt(1_000_000_000), + } + DefaultTestPerpOIs = []perptypes.OpenInterestDelta{ + BtcUsd_OpenInterest1_AtomicRes8, + EthUsd_OpenInterest1_AtomicRes9, + } +) + // Perpetual genesis parameters. const TestFundingRateClampFactorPpm = 6_000_000 const TestPremiumVoteClampFactorPpm = 60_000_000 @@ -237,6 +264,19 @@ var ( FundingIndex: dtypes.ZeroInt(), OpenInterest: dtypes.ZeroInt(), } + BtcUsd_20PercentInitial_10PercentMaintenance_25mmLowerCap_50mmUpperCap = perptypes.Perpetual{ + Params: perptypes.PerpetualParams{ + Id: 0, + Ticker: "BTC-USD 20/10 margin requirements", + MarketId: uint32(0), + AtomicResolution: int32(-8), + DefaultFundingPpm: int32(0), + LiquidityTier: uint32(9), + MarketType: perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + }, + FundingIndex: dtypes.ZeroInt(), + OpenInterest: dtypes.ZeroInt(), + } BtcUsd_NoMarginRequirement = perptypes.Perpetual{ Params: perptypes.PerpetualParams{ Id: 0, diff --git a/protocol/testutil/perpetuals/perpetuals.go b/protocol/testutil/perpetuals/perpetuals.go index dbcce92229..bde3204aff 100644 --- a/protocol/testutil/perpetuals/perpetuals.go +++ b/protocol/testutil/perpetuals/perpetuals.go @@ -2,8 +2,13 @@ package perpetuals import ( "math/big" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" "github.com/dydxprotocol/v4-chain/protocol/dtypes" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" ) @@ -114,3 +119,27 @@ func abs(n int64) int64 { } return n } + +// Helper function to set up default open interest for input perpetuals. +func SetUpDefaultPerpOIsForTest( + t *testing.T, + ctx sdk.Context, + k perptypes.PerpetualsKeeper, + perps []perptypes.Perpetual, +) { + for _, perpOI := range constants.DefaultTestPerpOIs { + for _, perp := range perps { + if perp.Params.Id != perpOI.PerpetualId { + continue + } + // If the perpetual exists in input, set up the open interest. + require.NoError(t, + k.ModifyOpenInterest( + ctx, + perp.Params.Id, + perpOI.BaseQuantumsDelta, + ), + ) + } + } +} diff --git a/protocol/x/clob/keeper/deleveraging_test.go b/protocol/x/clob/keeper/deleveraging_test.go index fa13dd960e..1b28708043 100644 --- a/protocol/x/clob/keeper/deleveraging_test.go +++ b/protocol/x/clob/keeper/deleveraging_test.go @@ -18,6 +18,7 @@ import ( clobtest "github.com/dydxprotocol/v4-chain/protocol/testutil/clob" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" + perptest "github.com/dydxprotocol/v4-chain/protocol/testutil/perpetuals" assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" blocktimetypes "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/types" "github.com/dydxprotocol/v4-chain/protocol/x/clob/memclob" @@ -766,6 +767,13 @@ func TestOffsetSubaccountPerpetualPosition(t *testing.T) { require.NoError(t, err) } + perptest.SetUpDefaultPerpOIsForTest( + t, + ks.Ctx, + ks.PerpetualsKeeper, + perps, + ) + clobPairs := []types.ClobPair{ constants.ClobPair_Btc, constants.ClobPair_Eth, @@ -1214,10 +1222,11 @@ func TestProcessDeleveraging(t *testing.T) { err := keepertest.CreateUsdcAsset(ks.Ctx, ks.AssetsKeeper) require.NoError(t, err) - for _, p := range []perptypes.Perpetual{ + testPerps := []perptypes.Perpetual{ constants.BtcUsd_20PercentInitial_10PercentMaintenance, constants.EthUsd_20PercentInitial_10PercentMaintenance, - } { + } + for _, p := range testPerps { _, err := ks.PerpetualsKeeper.CreatePerpetual( ks.Ctx, p.Params.Id, @@ -1231,6 +1240,13 @@ func TestProcessDeleveraging(t *testing.T) { require.NoError(t, err) } + perptest.SetUpDefaultPerpOIsForTest( + t, + ks.Ctx, + ks.PerpetualsKeeper, + testPerps, + ) + ks.SubaccountsKeeper.SetSubaccount(ks.Ctx, tc.liquidatedSubaccount) ks.SubaccountsKeeper.SetSubaccount(ks.Ctx, tc.offsettingSubaccount) @@ -1425,10 +1441,11 @@ func TestProcessDeleveragingAtOraclePrice(t *testing.T) { err := keepertest.CreateUsdcAsset(ks.Ctx, ks.AssetsKeeper) require.NoError(t, err) - for _, p := range []perptypes.Perpetual{ + testPerps := []perptypes.Perpetual{ constants.BtcUsd_20PercentInitial_10PercentMaintenance, constants.EthUsd_20PercentInitial_10PercentMaintenance, - } { + } + for _, p := range testPerps { _, err := ks.PerpetualsKeeper.CreatePerpetual( ks.Ctx, p.Params.Id, @@ -1442,6 +1459,13 @@ func TestProcessDeleveragingAtOraclePrice(t *testing.T) { require.NoError(t, err) } + perptest.SetUpDefaultPerpOIsForTest( + t, + ks.Ctx, + ks.PerpetualsKeeper, + testPerps, + ) + ks.SubaccountsKeeper.SetSubaccount(ks.Ctx, tc.liquidatedSubaccount) ks.SubaccountsKeeper.SetSubaccount(ks.Ctx, tc.offsettingSubaccount) @@ -1587,10 +1611,11 @@ func TestProcessDeleveraging_Rounding(t *testing.T) { err := keepertest.CreateUsdcAsset(ks.Ctx, ks.AssetsKeeper) require.NoError(t, err) - for _, p := range []perptypes.Perpetual{ + testPerps := []perptypes.Perpetual{ constants.BtcUsd_20PercentInitial_10PercentMaintenance, constants.EthUsd_20PercentInitial_10PercentMaintenance, - } { + } + for _, p := range testPerps { _, err := ks.PerpetualsKeeper.CreatePerpetual( ks.Ctx, p.Params.Id, @@ -1604,6 +1629,13 @@ func TestProcessDeleveraging_Rounding(t *testing.T) { require.NoError(t, err) } + perptest.SetUpDefaultPerpOIsForTest( + t, + ks.Ctx, + ks.PerpetualsKeeper, + testPerps, + ) + ks.SubaccountsKeeper.SetSubaccount(ks.Ctx, tc.liquidatedSubaccount) ks.SubaccountsKeeper.SetSubaccount(ks.Ctx, tc.offsettingSubaccount) bankruptcyPriceQuoteQuantums, err := ks.ClobKeeper.GetBankruptcyPriceInQuoteQuantums( diff --git a/protocol/x/clob/keeper/liquidations_test.go b/protocol/x/clob/keeper/liquidations_test.go index 799e922321..8d89d59975 100644 --- a/protocol/x/clob/keeper/liquidations_test.go +++ b/protocol/x/clob/keeper/liquidations_test.go @@ -20,6 +20,7 @@ import ( clobtest "github.com/dydxprotocol/v4-chain/protocol/testutil/clob" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" + perptest "github.com/dydxprotocol/v4-chain/protocol/testutil/perpetuals" blocktimetypes "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/types" "github.com/dydxprotocol/v4-chain/protocol/x/clob/memclob" "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" @@ -299,6 +300,13 @@ func TestPlacePerpetualLiquidation(t *testing.T) { require.NoError(t, err) } + perptest.SetUpDefaultPerpOIsForTest( + t, + ks.Ctx, + ks.PerpetualsKeeper, + tc.perpetuals, + ) + // Create all subaccounts. for _, subaccount := range tc.subaccounts { ks.SubaccountsKeeper.SetSubaccount(ctx, subaccount) @@ -1139,10 +1147,11 @@ func TestPlacePerpetualLiquidation_PreexistingLiquidation(t *testing.T) { err := keepertest.CreateUsdcAsset(ctx, ks.AssetsKeeper) require.NoError(t, err) - for _, perpetual := range []perptypes.Perpetual{ + testPerps := []perptypes.Perpetual{ constants.BtcUsd_100PercentMarginRequirement, constants.EthUsd_100PercentMarginRequirement, - } { + } + for _, perpetual := range testPerps { _, err = ks.PerpetualsKeeper.CreatePerpetual( ctx, perpetual.Params.Id, @@ -1156,6 +1165,13 @@ func TestPlacePerpetualLiquidation_PreexistingLiquidation(t *testing.T) { require.NoError(t, err) } + perptest.SetUpDefaultPerpOIsForTest( + t, + ks.Ctx, + ks.PerpetualsKeeper, + testPerps, + ) + for _, s := range tc.subaccounts { ks.SubaccountsKeeper.SetSubaccount(ctx, s) } @@ -2047,6 +2063,13 @@ func TestPlacePerpetualLiquidation_Deleveraging(t *testing.T) { require.NoError(t, err) } + perptest.SetUpDefaultPerpOIsForTest( + t, + ks.Ctx, + ks.PerpetualsKeeper, + perpetuals, + ) + for _, s := range tc.subaccounts { ks.SubaccountsKeeper.SetSubaccount(ctx, s) } @@ -2760,6 +2783,13 @@ func TestGetBankruptcyPriceInQuoteQuantums(t *testing.T) { require.NoError(t, err) } + perptest.SetUpDefaultPerpOIsForTest( + t, + ks.Ctx, + ks.PerpetualsKeeper, + tc.perpetuals, + ) + // Create the subaccount. subaccountId := satypes.SubaccountId{ Owner: "liquidations_test", @@ -2800,7 +2830,7 @@ func TestGetBankruptcyPriceInQuoteQuantums(t *testing.T) { }, }, }, - satypes.Match, + satypes.CollatCheck, ) require.True(t, success) @@ -4700,6 +4730,13 @@ func TestMaybeGetLiquidationOrder(t *testing.T) { require.NoError(t, err) } + perptest.SetUpDefaultPerpOIsForTest( + t, + ks.Ctx, + ks.PerpetualsKeeper, + tc.perpetuals, + ) + // Create all subaccounts. for _, subaccount := range tc.subaccounts { ks.SubaccountsKeeper.SetSubaccount(ctx, subaccount) diff --git a/protocol/x/clob/keeper/orders.go b/protocol/x/clob/keeper/orders.go index 23b4324c63..2a05e2c334 100644 --- a/protocol/x/clob/keeper/orders.go +++ b/protocol/x/clob/keeper/orders.go @@ -1098,7 +1098,7 @@ func (k Keeper) AddOrderToOrderbookCollatCheck( success, successPerSubaccountUpdate, err := k.subaccountsKeeper.CanUpdateSubaccounts( ctx, updates, - satypes.Match, + satypes.CollatCheck, ) // TODO(DEC-191): Remove the error case from `CanUpdateSubaccounts`, which can only occur on overflow and specifying // duplicate accounts. diff --git a/protocol/x/clob/keeper/orders_test.go b/protocol/x/clob/keeper/orders_test.go index dfc374a8c0..0912d2be24 100644 --- a/protocol/x/clob/keeper/orders_test.go +++ b/protocol/x/clob/keeper/orders_test.go @@ -11,6 +11,7 @@ import ( cmt "github.com/cometbft/cometbft/types" testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + perptest "github.com/dydxprotocol/v4-chain/protocol/testutil/perpetuals" "github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager" @@ -684,6 +685,13 @@ func TestPlaceShortTermOrder(t *testing.T) { require.NoError(t, err) } + perptest.SetUpDefaultPerpOIsForTest( + t, + ks.Ctx, + ks.PerpetualsKeeper, + tc.perpetuals, + ) + // Create all subaccounts. for _, subaccount := range tc.subaccounts { ks.SubaccountsKeeper.SetSubaccount(ctx, subaccount) @@ -912,6 +920,13 @@ func TestAddPreexistingStatefulOrder(t *testing.T) { require.NoError(t, err) } + perptest.SetUpDefaultPerpOIsForTest( + t, + ks.Ctx, + ks.PerpetualsKeeper, + tc.perpetuals, + ) + // Create all subaccounts. for _, subaccount := range tc.subaccounts { ks.SubaccountsKeeper.SetSubaccount(ctx, subaccount) diff --git a/protocol/x/perpetuals/keeper/perpetual.go b/protocol/x/perpetuals/keeper/perpetual.go index 1603f5d8bf..bcaad7caff 100644 --- a/protocol/x/perpetuals/keeper/perpetual.go +++ b/protocol/x/perpetuals/keeper/perpetual.go @@ -1257,8 +1257,9 @@ func (k Keeper) ModifyOpenInterest( if bigOpenInterest.Sign() < 0 { return errorsmod.Wrapf( types.ErrOpenInterestWouldBecomeNegative, - "perpetualId = %d, openInterest = %s", + "perpetualId = %d, openInterest before = %s, after = %s", perpetualId, + perpetual.OpenInterest.String(), bigOpenInterest.String(), ) } diff --git a/protocol/x/perpetuals/types/types.go b/protocol/x/perpetuals/types/types.go index 84cfaacd6e..81ba665c2b 100644 --- a/protocol/x/perpetuals/types/types.go +++ b/protocol/x/perpetuals/types/types.go @@ -86,6 +86,13 @@ type PerpetualsKeeper interface { defaultFundingPpm int32, liquidityTier uint32, ) (Perpetual, error) + ModifyOpenInterest( + ctx sdk.Context, + perpetualId uint32, + openInterestDeltaBaseQuantums *big.Int, + ) ( + err error, + ) SetLiquidityTier( ctx sdk.Context, id uint32, @@ -112,3 +119,10 @@ type PerpetualsKeeper interface { ctx sdk.Context, ) []Perpetual } + +type OpenInterestDelta struct { + // The `Id` of the `Perpetual`. + PerpetualId uint32 + // Delta of open interest (in base quantums). + BaseQuantumsDelta *big.Int +} diff --git a/protocol/x/subaccounts/keeper/oimf.go b/protocol/x/subaccounts/keeper/oimf.go new file mode 100644 index 0000000000..8fe7f18ca3 --- /dev/null +++ b/protocol/x/subaccounts/keeper/oimf.go @@ -0,0 +1,89 @@ +package keeper + +import ( + "fmt" + "math/big" + + "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" +) + +// Helper function to compute the delta long for a single settled update on a perpetual. +func getDeltaLongFromSettledUpdate( + u SettledUpdate, + updatedPerpId uint32, +) ( + deltaLong *big.Int, +) { + var perpPosition *types.PerpetualPosition + for _, p := range u.SettledSubaccount.PerpetualPositions { + // TODO use a pre-populated map + if p.PerpetualId == updatedPerpId { + perpPosition = p + } + } + + prevQuantums := perpPosition.GetBigQuantums() + afterQuantums := new(big.Int).Add( + prevQuantums, + u.PerpetualUpdates[0].GetBigQuantums(), + ) + + prevLong := prevQuantums // re-use pointer for efficiency + if prevLong.Sign() < 0 { + prevLong.SetUint64(0) + } + afterLong := afterQuantums // re-use pointer for efficiency + if afterLong.Sign() < 0 { + afterLong.SetUint64(0) + } + + return afterLong.Sub( + afterLong, + prevLong, + ) +} + +// Returns the delta open_interest for a pair of Match updates if they were applied. +func GetDeltaOpenInterestFromPerpMatchUpdates( + settledUpdates []SettledUpdate, +) ( + updatedPerpId uint32, + deltaOpenInterest *big.Int, +) { + if len(settledUpdates) != 2 { + panic( + fmt.Sprintf( + types.ErrMatchUpdatesMustHaveTwoUpdates, + settledUpdates, + ), + ) + } + + if len(settledUpdates[0].PerpetualUpdates) != 1 || len(settledUpdates[1].PerpetualUpdates) != 1 { + panic( + fmt.Sprintf( + types.ErrMatchUpdatesMustUpdateOnePerp, + settledUpdates, + ), + ) + } + + if settledUpdates[0].PerpetualUpdates[0].PerpetualId != settledUpdates[1].PerpetualUpdates[0].PerpetualId { + panic( + fmt.Sprintf( + types.ErrMatchUpdatesMustBeSamePerpId, + settledUpdates, + ), + ) + } else { + updatedPerpId = settledUpdates[0].PerpetualUpdates[0].PerpetualId + } + + deltaOpenInterest = big.NewInt(0) + for _, u := range settledUpdates { + deltaLong := getDeltaLongFromSettledUpdate(u, updatedPerpId) + deltaOpenInterest.Add(deltaOpenInterest, deltaLong) + } + + return updatedPerpId, deltaOpenInterest +} diff --git a/protocol/x/subaccounts/keeper/oimf_test.go b/protocol/x/subaccounts/keeper/oimf_test.go new file mode 100644 index 0000000000..27d4c8b56b --- /dev/null +++ b/protocol/x/subaccounts/keeper/oimf_test.go @@ -0,0 +1,302 @@ +package keeper_test + +import ( + "fmt" + "math/big" + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/dtypes" + keeper "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/keeper" + "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + "github.com/stretchr/testify/require" +) + +var ( + aliceSubaccountId = &types.SubaccountId{ + Owner: "Alice", + } + bobSubaccountId = &types.SubaccountId{ + Owner: "Bob", + } +) + +func TestGetDeltaOpenInterestFromPerpMatchUpdates(t *testing.T) { + tests := map[string]struct { + settledUpdates []keeper.SettledUpdate + expectedDelta *big.Int + expectedUpdatedPerpId uint32 + panicErr string + }{ + "Invalid: 1 update": { + settledUpdates: []keeper.SettledUpdate{ + { + SettledSubaccount: types.Subaccount{}, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 0, + BigQuantumsDelta: big.NewInt(1_000), + }, + }, + }, + }, + panicErr: types.ErrMatchUpdatesMustHaveTwoUpdates, + }, + "Invalid: one of the updates contains no perp update": { + settledUpdates: []keeper.SettledUpdate{ + { + SettledSubaccount: types.Subaccount{ + Id: aliceSubaccountId, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 0, + BigQuantumsDelta: big.NewInt(1_000), + }, + }, + }, + { + SettledSubaccount: types.Subaccount{ + Id: bobSubaccountId, + }, + }, + }, + panicErr: types.ErrMatchUpdatesMustUpdateOnePerp, + }, + "Invalid: updates are on different perpetuals": { + settledUpdates: []keeper.SettledUpdate{ + { + SettledSubaccount: types.Subaccount{ + Id: aliceSubaccountId, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 0, + BigQuantumsDelta: big.NewInt(1_000), + }, + }, + }, + { + SettledSubaccount: types.Subaccount{ + Id: bobSubaccountId, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1, + BigQuantumsDelta: big.NewInt(1_000), + }, + }, + }, + }, + panicErr: types.ErrMatchUpdatesMustBeSamePerpId, + }, + "Valid: 0 -> -500, 0 -> 500, delta = 500": { + settledUpdates: []keeper.SettledUpdate{ + { + SettledSubaccount: types.Subaccount{ + Id: aliceSubaccountId, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1, + BigQuantumsDelta: big.NewInt(500), + }, + }, + }, + { + SettledSubaccount: types.Subaccount{ + Id: bobSubaccountId, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1, + BigQuantumsDelta: big.NewInt(-500), + }, + }, + }, + }, + expectedUpdatedPerpId: 1, + expectedDelta: big.NewInt(500), + }, + "Valid: 500 -> 0, 0 -> 500, delta = 0": { + settledUpdates: []keeper.SettledUpdate{ + { + SettledSubaccount: types.Subaccount{ + Id: aliceSubaccountId, + PerpetualPositions: []*types.PerpetualPosition{}, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1000, + BigQuantumsDelta: big.NewInt(500), + }, + }, + }, + { + SettledSubaccount: types.Subaccount{ + Id: bobSubaccountId, + PerpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: 1000, + Quantums: dtypes.NewInt(500), + }, + }, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1000, + BigQuantumsDelta: big.NewInt(-500), + }, + }, + }, + }, + expectedUpdatedPerpId: 1000, + expectedDelta: big.NewInt(0), + }, + "Valid: 500 -> 350, 0 -> 150, delta = 0": { + settledUpdates: []keeper.SettledUpdate{ + { + SettledSubaccount: types.Subaccount{ + Id: aliceSubaccountId, + PerpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: 1000, + Quantums: dtypes.NewInt(500), + }, + }, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1000, + BigQuantumsDelta: big.NewInt(-150), + }, + }, + }, + { + SettledSubaccount: types.Subaccount{ + Id: bobSubaccountId, + PerpetualPositions: []*types.PerpetualPosition{}, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1000, + BigQuantumsDelta: big.NewInt(150), + }, + }, + }, + }, + expectedUpdatedPerpId: 1000, + expectedDelta: big.NewInt(0), + }, + "Valid: -100 -> 200, 250 -> -50, delta = -50": { + settledUpdates: []keeper.SettledUpdate{ + { + SettledSubaccount: types.Subaccount{ + Id: aliceSubaccountId, + PerpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: 1000, + Quantums: dtypes.NewInt(-100), + }, + }, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1000, + BigQuantumsDelta: big.NewInt(300), + }, + }, + }, + { + SettledSubaccount: types.Subaccount{ + Id: bobSubaccountId, + PerpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: 1000, + Quantums: dtypes.NewInt(250), + }, + }, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1000, + BigQuantumsDelta: big.NewInt(-300), + }, + }, + }, + }, + expectedUpdatedPerpId: 1000, + expectedDelta: big.NewInt(-50), + }, + "Valid: -3100 -> -5000, 1000 -> 2900, delta = 1900": { + settledUpdates: []keeper.SettledUpdate{ + { + SettledSubaccount: types.Subaccount{ + Id: aliceSubaccountId, + PerpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: 1000, + Quantums: dtypes.NewInt(-3100), + }, + }, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1000, + BigQuantumsDelta: big.NewInt(-1900), + }, + }, + }, + { + SettledSubaccount: types.Subaccount{ + Id: bobSubaccountId, + PerpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: 1000, + Quantums: dtypes.NewInt(1000), + }, + }, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1000, + BigQuantumsDelta: big.NewInt(+1900), + }, + }, + }, + }, + expectedUpdatedPerpId: 1000, + expectedDelta: big.NewInt(1900), + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + if tc.panicErr != "" { + require.PanicsWithValue(t, + fmt.Sprintf( + tc.panicErr, + tc.settledUpdates, + ), func() { + keeper.GetDeltaOpenInterestFromPerpMatchUpdates(tc.settledUpdates) + }, + ) + return + } + + updatedPerpId, deltaOpenInterest := keeper.GetDeltaOpenInterestFromPerpMatchUpdates(tc.settledUpdates) + require.Equal( + t, + tc.expectedUpdatedPerpId, + updatedPerpId, + ) + fmt.Printf("deltaOpenInterest: %+v, tc.expectedDelta: %+v\n", deltaOpenInterest == nil, tc.expectedDelta == nil) + require.Zerof( + t, + tc.expectedDelta.Cmp(deltaOpenInterest), + "deltaOpenInterest: %v, tc.expectedDelta: %v", + deltaOpenInterest, + tc.expectedDelta, + ) + }) + } +} diff --git a/protocol/x/subaccounts/keeper/subaccount.go b/protocol/x/subaccounts/keeper/subaccount.go index 55a1b5fb14..b53a93e7be 100644 --- a/protocol/x/subaccounts/keeper/subaccount.go +++ b/protocol/x/subaccounts/keeper/subaccount.go @@ -629,6 +629,17 @@ func (k Keeper) internalCanUpdateSubaccounts( } } + var perpOpenInterestDelta *perptypes.OpenInterestDelta + // If update type is `Match`, calculate the open interest delta for the updated perpetual. + // Note: this assumes that `Match` can only happen for perpetuals. + if updateType == types.Match { + updatedPerpId, deltaOpenInterest := GetDeltaOpenInterestFromPerpMatchUpdates(settledUpdates) + perpOpenInterestDelta = &perptypes.OpenInterestDelta{ + PerpetualId: updatedPerpId, + BaseQuantumsDelta: deltaOpenInterest, + } + } + bigCurNetCollateral := make(map[string]*big.Int) bigCurInitialMargin := make(map[string]*big.Int) bigCurMaintenanceMargin := make(map[string]*big.Int) @@ -651,11 +662,52 @@ func (k Keeper) internalCanUpdateSubaccounts( } } + // Temporily apply open interest delta to perpetuals, so IMF is calculated based on open interest after the update. + // `perpOpenInterestDeltas` is only present for `Match` update type. + if perpOpenInterestDelta != nil { + if err := k.perpetualsKeeper.ModifyOpenInterest( + ctx, + perpOpenInterestDelta.PerpetualId, + perpOpenInterestDelta.BaseQuantumsDelta, + ); err != nil { + return false, nil, errorsmod.Wrapf( + types.ErrCannotModifyPerpOpenInterestForOIMF, + "perpId = %v, delta = %v, settledUpdates = %+v, err = %v", + perpOpenInterestDelta.PerpetualId, + perpOpenInterestDelta.BaseQuantumsDelta, + settledUpdates, + err, + ) + } + } // Get the new collateralization and margin requirements with the update applied. bigNewNetCollateral, bigNewInitialMargin, bigNewMaintenanceMargin, - err := k.internalGetNetCollateralAndMarginRequirements(ctx, u) + err := k.internalGetNetCollateralAndMarginRequirements( + ctx, + u, + ) + + // Revert the temporary open interest delta to perpetuals. + if perpOpenInterestDelta != nil { + if err := k.perpetualsKeeper.ModifyOpenInterest( + ctx, + perpOpenInterestDelta.PerpetualId, + new(big.Int).Neg( + perpOpenInterestDelta.BaseQuantumsDelta, + ), + ); err != nil { + return false, nil, errorsmod.Wrapf( + types.ErrCannotRevertPerpOpenInterestForOIMF, + "perpId = %v, delta = %v, settledUpdates = %+v", + perpOpenInterestDelta.PerpetualId, + perpOpenInterestDelta.BaseQuantumsDelta, + settledUpdates, + ) + } + } + // if `internalGetNetCollateralAndMarginRequirements`, returns error. if err != nil { return false, nil, err } @@ -882,7 +934,11 @@ func (k Keeper) internalGetNetCollateralAndMarginRequirements( bigInitialMarginRequirements, bigMaintenanceMarginRequirements, - err := pk.GetMarginRequirements(ctx, id, bigQuantums) + err := pk.GetMarginRequirements( + ctx, + id, + bigQuantums, + ) if err != nil { return err } diff --git a/protocol/x/subaccounts/keeper/subaccount_test.go b/protocol/x/subaccounts/keeper/subaccount_test.go index bfb5bb2864..7b50ab3c56 100644 --- a/protocol/x/subaccounts/keeper/subaccount_test.go +++ b/protocol/x/subaccounts/keeper/subaccount_test.go @@ -2547,7 +2547,7 @@ func TestUpdateSubaccounts(t *testing.T) { tc.updates[i] = u } - success, successPerUpdate, err := keeper.UpdateSubaccounts(ctx, tc.updates, types.Match) + success, successPerUpdate, err := keeper.UpdateSubaccounts(ctx, tc.updates, types.CollatCheck) if tc.expectedErr != nil { require.ErrorIs(t, tc.expectedErr, err) } else { @@ -2884,7 +2884,7 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { assetPositions: testutil.CreateUsdcAssetPosition(big.NewInt(25_000_000_000)), // $25,000 expectedQuoteBalance: big.NewInt(0), expectedSuccess: true, - expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, + expectedSuccessPerUpdate: []types.UpdateResult{types.Success, types.Success}, perpetuals: []perptypes.Perpetual{ constants.BtcUsd_NoMarginRequirement, }, @@ -2918,6 +2918,16 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, }, }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(25_000_000_000)), // $25,000 + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-50_000_000), // .5 BTC + }, + }, + }, }, msgSenderEnabled: false, @@ -2931,7 +2941,7 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { assetPositions: testutil.CreateUsdcAssetPosition(big.NewInt(25_000_000_000)), // $25,000 expectedQuoteBalance: big.NewInt(0), expectedSuccess: true, - expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, + expectedSuccessPerUpdate: []types.UpdateResult{types.Success, types.Success}, perpetuals: []perptypes.Perpetual{ constants.BtcUsd_NoMarginRequirement, }, @@ -2965,6 +2975,16 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, }, }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(25_000_000_000)), // $25,000 + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-50_000_000), // .5 BTC + }, + }, + }, }, msgSenderEnabled: false, @@ -2978,7 +2998,7 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { assetPositions: testutil.CreateUsdcAssetPosition(big.NewInt(25_000_000_000)), // $25,000 expectedQuoteBalance: big.NewInt(0), expectedSuccess: true, - expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, + expectedSuccessPerUpdate: []types.UpdateResult{types.Success, types.Success}, perpetuals: []perptypes.Perpetual{ constants.BtcUsd_NoMarginRequirement, }, @@ -3012,6 +3032,16 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, }, }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(25_000_000_000)), // $25,000 + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-50_000_000), // .5 BTC + }, + }, + }, }, msgSenderEnabled: false, @@ -3021,9 +3051,12 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { updateType: types.Match, }, "undercollateralized matches are not blocked if negative TNC subaccount was seen at current block": { - expectedQuoteBalance: big.NewInt(0), - expectedSuccess: false, - expectedSuccessPerUpdate: []types.UpdateResult{types.NewlyUndercollateralized}, + expectedQuoteBalance: big.NewInt(0), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.NewlyUndercollateralized, + types.NewlyUndercollateralized, + }, perpetuals: []perptypes.Perpetual{ constants.BtcUsd_SmallMarginRequirement, }, @@ -3043,6 +3076,16 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, }, }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(50_000_000_000)), // $50,000 + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-100_000_000), // -1 BTC + }, + }, + }, }, msgSenderEnabled: true, @@ -3053,9 +3096,12 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, `undercollateralized matches are not blocked if current block is within WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS`: { - expectedQuoteBalance: big.NewInt(0), - expectedSuccess: false, - expectedSuccessPerUpdate: []types.UpdateResult{types.NewlyUndercollateralized}, + expectedQuoteBalance: big.NewInt(0), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.NewlyUndercollateralized, + types.NewlyUndercollateralized, + }, perpetuals: []perptypes.Perpetual{ constants.BtcUsd_SmallMarginRequirement, }, @@ -3075,6 +3121,16 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, }, }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(50_000_000_000)), // $50,000 + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-100_000_000), // 1 BTC + }, + }, + }, }, msgSenderEnabled: true, @@ -3085,9 +3141,12 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { updateType: types.Match, }, "undercollateralized matches are not blocked if negative TNC subaccount was never seen": { - expectedQuoteBalance: big.NewInt(0), - expectedSuccess: false, - expectedSuccessPerUpdate: []types.UpdateResult{types.NewlyUndercollateralized}, + expectedQuoteBalance: big.NewInt(0), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.NewlyUndercollateralized, + types.NewlyUndercollateralized, + }, perpetuals: []perptypes.Perpetual{ constants.BtcUsd_SmallMarginRequirement, }, @@ -3107,6 +3166,16 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, }, }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(50_000_000_000)), // $50,000 + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-100_000_000), // -1 BTC + }, + }, + }, }, msgSenderEnabled: true, @@ -3379,20 +3448,386 @@ func TestCanUpdateSubaccounts(t *testing.T) { perpetuals []perptypes.Perpetual assets []*asstypes.Asset marketParamPrices []pricestypes.MarketParamPrice + openInterests []perptypes.OpenInterestDelta // Subaccount state. - useEmptySubaccount bool - perpetualPositions []*types.PerpetualPosition - assetPositions []*types.AssetPosition + useEmptySubaccount bool + perpetualPositions []*types.PerpetualPosition + assetPositions []*types.AssetPosition + additionalTestSubaccounts []types.Subaccount // Updates. - updates []types.Update + updates []types.Update + updateType types.UpdateType // Expectations. expectedSuccess bool expectedSuccessPerUpdate []types.UpdateResult expectedErr error }{ + "(OIMF) OI increased, still at base IMF, match is collateralized": { + expectedSuccess: true, + expectedSuccessPerUpdate: []types.UpdateResult{types.Success, types.Success}, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + }, + assetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + // 900_000 USDC (just enough to colalteralize 90 BTC at $50_000 and 20% IMF) + Quantums: dtypes.NewInt(900_000_000_000), + }, + }, + additionalTestSubaccounts: []types.Subaccount{ + { + Id: &types.SubaccountId{ + Owner: "Bob", + Number: 0, + }, + AssetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + // 900_000 USDC (just enough to colalteralize 90 BTC at $50_000 and 20% IMF) + Quantums: dtypes.NewInt(900_000_000_000), + }, + }, + }, + }, + updates: []types.Update{ + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(9_000_000_000), // 90 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(-4_500_000_000_000), // -4,500,000 USDC + }, + }, + SubaccountId: types.SubaccountId{ + Owner: "Bob", + Number: 0, + }, + }, + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-9_000_000_000), // 9 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(4_500_000_000_000), // 4,500,000 USDC + }, + }, + }, + }, + updateType: types.Match, + }, + "(OIMF) current OI soft lower cap, match collateralized at base IMF but not OIMF": { + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.NewlyUndercollateralized, + types.NewlyUndercollateralized, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_25mmLowerCap_50mmUpperCap, + }, + assetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + // 900_000 USDC (just enough to colalteralize 90 BTC at $50_000 and 20% IMF) + Quantums: dtypes.NewInt(900_000_000_000), + }, + }, + additionalTestSubaccounts: []types.Subaccount{ + { + Id: &types.SubaccountId{ + Owner: "Bob", + Number: 0, + }, + AssetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + // 900_000 USDC (just enough to colalteralize 90 BTC at $50_000 and 20% IMF) + Quantums: dtypes.NewInt(900_000_000_000), + }, + }, + }, + }, + openInterests: []perptypes.OpenInterestDelta{ + { + PerpetualId: uint32(0), + // 500 BTC. At $50,000, this is $25,000,000 of OI. + BaseQuantumsDelta: big.NewInt(50_000_000_000), + }, + }, + updates: []types.Update{ + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(9_000_000_000), // 90 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(-4_500_000_000_000), // -4,500,000 USDC + }, + }, + SubaccountId: types.SubaccountId{ + Owner: "Bob", + Number: 0, + }, + }, + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-9_000_000_000), // 9 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(4_500_000_000_000), // 4,500,000 USDC + }, + }, + }, + }, + updateType: types.Match, + }, + "(OIMF) match collateralized at base IMF and just collateralized at OIMF": { + expectedSuccess: true, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.Success, + types.Success, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_25mmLowerCap_50mmUpperCap, + }, + assetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + // 900_000 USDC (just enough to colalteralize 90 BTC at $50_000 and 20% IMF) + Quantums: dtypes.NewInt(900_000_000_000), + }, + }, + additionalTestSubaccounts: []types.Subaccount{ + { + Id: &types.SubaccountId{ + Owner: "Bob", + Number: 0, + }, + AssetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + // 900_000 USDC (just enough to colalteralize 90 BTC at $50_000 and 20% IMF) + Quantums: dtypes.NewInt(900_000_000_000), + }, + }, + }, + }, + openInterests: []perptypes.OpenInterestDelta{ + { + PerpetualId: uint32(0), + // (Only difference from prevoius test case) + // 410 BTC. At $50,000, this is $20,500,000 of OI. + // OI would be $25,000,000 after the Match updates, so OIMF is still at base IMF. + BaseQuantumsDelta: big.NewInt(41_000_000_000), + }, + }, + updates: []types.Update{ + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(9_000_000_000), // 90 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(-4_500_000_000_000), // -4,500,000 USDC + }, + }, + SubaccountId: types.SubaccountId{ + Owner: "Bob", + Number: 0, + }, + }, + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-9_000_000_000), // 9 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(4_500_000_000_000), // 4,500,000 USDC + }, + }, + }, + }, + updateType: types.Match, + }, + "(OIMF) match collateralized at base IMF and just failed collateralization at OIMF": { + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.NewlyUndercollateralized, + types.NewlyUndercollateralized, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_25mmLowerCap_50mmUpperCap, + }, + assetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + // 900_000 USDC (just enough to colalteralize 90 BTC at $50_000 and 20% IMF) + Quantums: dtypes.NewInt(900_000_000_000), + }, + }, + additionalTestSubaccounts: []types.Subaccount{ + { + Id: &types.SubaccountId{ + Owner: "Bob", + Number: 0, + }, + AssetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + // 900_000 USDC (just enough to colalteralize 90 BTC at $50_000 and 20% IMF) + Quantums: dtypes.NewInt(900_000_000_000), + }, + }, + }, + }, + openInterests: []perptypes.OpenInterestDelta{ + { + PerpetualId: uint32(0), + // (Only difference from prevoius test case) + // 410 BTC + 1 base quantum. At $50,000, this is > $20,500,000 of OI. + // OI would be just past $25,000,000 after the Match updates, so OIMF > IMF = 20% + BaseQuantumsDelta: big.NewInt(41_000_000_001), + }, + }, + updates: []types.Update{ + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(9_000_000_000), // 90 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(-4_500_000_000_000), // -4,500,000 USDC + }, + }, + SubaccountId: types.SubaccountId{ + Owner: "Bob", + Number: 0, + }, + }, + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-9_000_000_000), // 9 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(4_500_000_000_000), // 4,500,000 USDC + }, + }, + }, + }, + updateType: types.Match, + }, + "(OIMF) OIMF caps at 100%, un-leveraged trade always succeeds": { + expectedSuccess: true, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.Success, + types.Success, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_25mmLowerCap_50mmUpperCap, + }, + assetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + // 4_500_000 USDC (just enough to collateralize 90 BTC at $50_000 and 100% IMF) + Quantums: dtypes.NewInt(4_500_000_000_000)}, + }, + additionalTestSubaccounts: []types.Subaccount{ + { + Id: &types.SubaccountId{ + Owner: "Bob", + Number: 0, + }, + AssetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + // 4_500_000 USDC (just enough to collateralize 90 BTC at $50_000 and 100% IMF) + Quantums: dtypes.NewInt(4_500_000_000_000), + }, + }, + }, + }, + openInterests: []perptypes.OpenInterestDelta{ + { + PerpetualId: uint32(0), + // 10_000 BTC. At $50,000, this is $500mm of OI which way past upper cap + BaseQuantumsDelta: big.NewInt(1_000_000_000_000), + }, + }, + updates: []types.Update{ + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(9_000_000_000), // 90 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(-4_500_000_000_000), // -4,500,000 USDC + }, + }, + SubaccountId: types.SubaccountId{ + Owner: "Bob", + Number: 0, + }, + }, + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-9_000_000_000), // 9 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(4_500_000_000_000), // 4,500,000 USDC + }, + }, + }, + }, + updateType: types.Match, + }, "one update with no existing position and no margin requirements": { expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, @@ -3417,6 +3852,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(1)), }, }, + updateType: types.Deposit, expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, }, @@ -3454,6 +3890,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { }, }, }, + updateType: types.Deposit, expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, }, @@ -3779,6 +4216,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { updates: []types.Update{ {}, }, + updateType: types.Deposit, expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, }, @@ -3799,6 +4237,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { updates: []types.Update{ {}, }, + updateType: types.Deposit, expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, }, @@ -3821,6 +4260,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(3)), // $3 }, }, + updateType: types.Deposit, expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, }, @@ -4042,6 +4482,15 @@ func TestCanUpdateSubaccounts(t *testing.T) { require.NoError(t, err) } + for _, openInterest := range tc.openInterests { + // Update open interest for each perpetual from default `0`. + require.NoError(t, perpetualsKeeper.ModifyOpenInterest( + ctx, + openInterest.PerpetualId, + openInterest.BaseQuantumsDelta, + )) + } + subaccountId := types.SubaccountId{Owner: "foo", Number: 0} if !tc.useEmptySubaccount { subaccount := createNSubaccount(keeper, ctx, 1, big.NewInt(1_000))[0] @@ -4058,7 +4507,16 @@ func TestCanUpdateSubaccounts(t *testing.T) { tc.updates[i] = u } - success, successPerUpdate, err := keeper.CanUpdateSubaccounts(ctx, tc.updates, types.Match) + for _, sa := range tc.additionalTestSubaccounts { + keeper.SetSubaccount(ctx, sa) + } + + // If test case has unspecified update type, use `CollatCheck` as default. + updateType := tc.updateType + if updateType == types.UpdateTypeUnspecified { + updateType = types.CollatCheck + } + success, successPerUpdate, err := keeper.CanUpdateSubaccounts(ctx, tc.updates, updateType) if tc.expectedErr != nil { require.ErrorIs(t, tc.expectedErr, err) } else { diff --git a/protocol/x/subaccounts/types/errors.go b/protocol/x/subaccounts/types/errors.go index 91b4da30d2..1b5315b187 100644 --- a/protocol/x/subaccounts/types/errors.go +++ b/protocol/x/subaccounts/types/errors.go @@ -7,6 +7,16 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/lib" ) +// Panic strings +const ( + ErrMatchUpdatesMustHaveTwoUpdates = "internalCanUpdateSubaccounts: MATCH subaccount updates must consist of " + + "exactly 2 updates, got settledUpdates: %+v" + ErrMatchUpdatesMustUpdateOnePerp = "internalCanUpdateSubaccounts: MATCH subaccount updates must each have " + + "exactly 1 PerpetualUpdate, got settledUpdates: %+v" + ErrMatchUpdatesMustBeSamePerpId = "internalCanUpdateSubaccounts: MATCH subaccount updates must consists two " + + "updates on same perpetual Id, got settledUpdates: %+v" +) + // x/subaccounts module sentinel errors var ( // 0 - 99: generic. @@ -38,7 +48,21 @@ var ( // 400 - 499: perpetual position related. ErrPerpPositionsOutOfOrder = errorsmod.Register(ModuleName, 400, "perpetual positions are out of order") - ErrPerpPositionZeroQuantum = errorsmod.Register(ModuleName, 401, "perpetual position's quantum cannot be zero") + ErrPerpPositionZeroQuantum = errorsmod.Register( + ModuleName, + 401, + "perpetual position's quantum cannot be zero", + ) + ErrCannotModifyPerpOpenInterestForOIMF = errorsmod.Register( + ModuleName, + 402, + "cannot temporarily modify perpetual open interest for OIMF calculation", + ) + ErrCannotRevertPerpOpenInterestForOIMF = errorsmod.Register( + ModuleName, + 403, + "cannot revert perpetual open interest for OIMF calculation", + ) // 500 - 599: transfer related. ErrAssetTransferQuantumsNotPositive = errorsmod.Register( diff --git a/protocol/x/subaccounts/types/expected_keepers.go b/protocol/x/subaccounts/types/expected_keepers.go index 4d30eb8174..8f0c9dd217 100644 --- a/protocol/x/subaccounts/types/expected_keepers.go +++ b/protocol/x/subaccounts/types/expected_keepers.go @@ -74,6 +74,7 @@ type PerpetualsKeeper interface { GetAllPerpetuals(ctx sdk.Context) []perptypes.Perpetual GetInsuranceFundName(ctx sdk.Context, perpetualId uint32) (string, error) GetInsuranceFundModuleAddress(ctx sdk.Context, perpetualId uint32) (sdk.AccAddress, error) + ModifyOpenInterest(ctx sdk.Context, perpetualId uint32, bigQuantums *big.Int) error } // BankKeeper defines the expected interface needed to retrieve account balances. diff --git a/protocol/x/subaccounts/types/update.go b/protocol/x/subaccounts/types/update.go index 684b618b28..e591bea40b 100644 --- a/protocol/x/subaccounts/types/update.go +++ b/protocol/x/subaccounts/types/update.go @@ -99,17 +99,21 @@ type PerpetualUpdate struct { type UpdateType uint const ( - Withdrawal UpdateType = iota + UpdateTypeUnspecified UpdateType = iota + Withdrawal Transfer Deposit Match + CollatCheck ) var updateTypeStringMap = map[UpdateType]string{ - Withdrawal: "Withdrawal", - Transfer: "Transfer", - Deposit: "Deposit", - Match: "Match", + UpdateTypeUnspecified: "UpdateTypeUnspecified", + Withdrawal: "Withdrawal", + Transfer: "Transfer", + Deposit: "Deposit", + Match: "Match", + CollatCheck: "CollatCheck", } func (u UpdateType) String() string { From 0cb4df5b676dd0f2574342f4613389ed664a5981 Mon Sep 17 00:00:00 2001 From: Teddy Ding Date: Wed, 20 Mar 2024 17:05:59 -0400 Subject: [PATCH 2/8] Use branched context, added unit test --- protocol/x/subaccounts/keeper/subaccount.go | 26 +++++-------------- .../x/subaccounts/keeper/subaccount_test.go | 12 +++++++++ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/protocol/x/subaccounts/keeper/subaccount.go b/protocol/x/subaccounts/keeper/subaccount.go index b53a93e7be..2de2db773a 100644 --- a/protocol/x/subaccounts/keeper/subaccount.go +++ b/protocol/x/subaccounts/keeper/subaccount.go @@ -662,11 +662,15 @@ func (k Keeper) internalCanUpdateSubaccounts( } } + // Branch the state to calculate the new OIMF after OI increase. + // The branched state is only needed for this purpose and is always discarded. + branchedContext, _ := ctx.CacheContext() + // Temporily apply open interest delta to perpetuals, so IMF is calculated based on open interest after the update. // `perpOpenInterestDeltas` is only present for `Match` update type. if perpOpenInterestDelta != nil { if err := k.perpetualsKeeper.ModifyOpenInterest( - ctx, + branchedContext, perpOpenInterestDelta.PerpetualId, perpOpenInterestDelta.BaseQuantumsDelta, ); err != nil { @@ -685,28 +689,10 @@ func (k Keeper) internalCanUpdateSubaccounts( bigNewInitialMargin, bigNewMaintenanceMargin, err := k.internalGetNetCollateralAndMarginRequirements( - ctx, + branchedContext, u, ) - // Revert the temporary open interest delta to perpetuals. - if perpOpenInterestDelta != nil { - if err := k.perpetualsKeeper.ModifyOpenInterest( - ctx, - perpOpenInterestDelta.PerpetualId, - new(big.Int).Neg( - perpOpenInterestDelta.BaseQuantumsDelta, - ), - ); err != nil { - return false, nil, errorsmod.Wrapf( - types.ErrCannotRevertPerpOpenInterestForOIMF, - "perpId = %v, delta = %v, settledUpdates = %+v", - perpOpenInterestDelta.PerpetualId, - perpOpenInterestDelta.BaseQuantumsDelta, - settledUpdates, - ) - } - } // if `internalGetNetCollateralAndMarginRequirements`, returns error. if err != nil { return false, nil, err diff --git a/protocol/x/subaccounts/keeper/subaccount_test.go b/protocol/x/subaccounts/keeper/subaccount_test.go index 7b50ab3c56..af1c2410f8 100644 --- a/protocol/x/subaccounts/keeper/subaccount_test.go +++ b/protocol/x/subaccounts/keeper/subaccount_test.go @@ -4524,6 +4524,18 @@ func TestCanUpdateSubaccounts(t *testing.T) { require.Equal(t, tc.expectedSuccessPerUpdate, successPerUpdate) require.Equal(t, tc.expectedSuccess, success) } + + for _, openInterest := range tc.openInterests { + // Check open interest for each perpetual did not change after the check. + perp, err := perpetualsKeeper.GetPerpetual(ctx, openInterest.PerpetualId) + require.NoError(t, err) + require.Zerof(t, + openInterest.BaseQuantumsDelta.Cmp(perp.OpenInterest.BigInt()), + "expected: %s, got: %s", + openInterest.BaseQuantumsDelta.String(), + perp.OpenInterest.String(), + ) + } }) } } From 5a3ee0ec17bdeeca7ee8b80811be87948643e678 Mon Sep 17 00:00:00 2001 From: Teddy Ding Date: Wed, 20 Mar 2024 22:55:44 -0400 Subject: [PATCH 3/8] nits --- protocol/x/subaccounts/keeper/oimf.go | 15 +++++- protocol/x/subaccounts/keeper/oimf_test.go | 54 +++++++++++++++++++ .../x/subaccounts/keeper/subaccount_test.go | 25 ++------- protocol/x/subaccounts/types/errors.go | 4 +- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/protocol/x/subaccounts/keeper/oimf.go b/protocol/x/subaccounts/keeper/oimf.go index 8fe7f18ca3..360faedd51 100644 --- a/protocol/x/subaccounts/keeper/oimf.go +++ b/protocol/x/subaccounts/keeper/oimf.go @@ -68,7 +68,10 @@ func GetDeltaOpenInterestFromPerpMatchUpdates( ) } - if settledUpdates[0].PerpetualUpdates[0].PerpetualId != settledUpdates[1].PerpetualUpdates[0].PerpetualId { + perpUpdate0 := settledUpdates[0].PerpetualUpdates[0] + perpUpdate1 := settledUpdates[1].PerpetualUpdates[0] + + if perpUpdate0.PerpetualId != perpUpdate1.PerpetualId { panic( fmt.Sprintf( types.ErrMatchUpdatesMustBeSamePerpId, @@ -79,6 +82,16 @@ func GetDeltaOpenInterestFromPerpMatchUpdates( updatedPerpId = settledUpdates[0].PerpetualUpdates[0].PerpetualId } + if (perpUpdate0.BigQuantumsDelta.Sign()*perpUpdate1.BigQuantumsDelta.Sign() > 0) || + perpUpdate0.BigQuantumsDelta.CmpAbs(perpUpdate1.BigQuantumsDelta) != 0 { + panic( + fmt.Sprintf( + types.ErrMatchUpdatesInvalidSize, + settledUpdates, + ), + ) + } + deltaOpenInterest = big.NewInt(0) for _, u := range settledUpdates { deltaLong := getDeltaLongFromSettledUpdate(u, updatedPerpId) diff --git a/protocol/x/subaccounts/keeper/oimf_test.go b/protocol/x/subaccounts/keeper/oimf_test.go index 27d4c8b56b..16bcce5eb7 100644 --- a/protocol/x/subaccounts/keeper/oimf_test.go +++ b/protocol/x/subaccounts/keeper/oimf_test.go @@ -89,6 +89,60 @@ func TestGetDeltaOpenInterestFromPerpMatchUpdates(t *testing.T) { }, panicErr: types.ErrMatchUpdatesMustBeSamePerpId, }, + "Invalid: updates don't have opposite signs": { + settledUpdates: []keeper.SettledUpdate{ + { + SettledSubaccount: types.Subaccount{ + Id: aliceSubaccountId, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1, + BigQuantumsDelta: big.NewInt(500), + }, + }, + }, + { + SettledSubaccount: types.Subaccount{ + Id: bobSubaccountId, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1, + BigQuantumsDelta: big.NewInt(500), + }, + }, + }, + }, + panicErr: types.ErrMatchUpdatesInvalidSize, + }, + "Invalid: updates don't have equal absolute base quantums": { + settledUpdates: []keeper.SettledUpdate{ + { + SettledSubaccount: types.Subaccount{ + Id: aliceSubaccountId, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1, + BigQuantumsDelta: big.NewInt(500), + }, + }, + }, + { + SettledSubaccount: types.Subaccount{ + Id: bobSubaccountId, + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 1, + BigQuantumsDelta: big.NewInt(-499), + }, + }, + }, + }, + panicErr: types.ErrMatchUpdatesInvalidSize, + }, "Valid: 0 -> -500, 0 -> 500, delta = 500": { settledUpdates: []keeper.SettledUpdate{ { diff --git a/protocol/x/subaccounts/keeper/subaccount_test.go b/protocol/x/subaccounts/keeper/subaccount_test.go index af1c2410f8..10cf995a33 100644 --- a/protocol/x/subaccounts/keeper/subaccount_test.go +++ b/protocol/x/subaccounts/keeper/subaccount_test.go @@ -3480,10 +3480,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { }, additionalTestSubaccounts: []types.Subaccount{ { - Id: &types.SubaccountId{ - Owner: "Bob", - Number: 0, - }, + Id: &constants.Bob_Num0, AssetPositions: []*types.AssetPosition{ { AssetId: uint32(0), @@ -3547,10 +3544,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { }, additionalTestSubaccounts: []types.Subaccount{ { - Id: &types.SubaccountId{ - Owner: "Bob", - Number: 0, - }, + Id: &constants.Bob_Num0, AssetPositions: []*types.AssetPosition{ { AssetId: uint32(0), @@ -3621,10 +3615,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { }, additionalTestSubaccounts: []types.Subaccount{ { - Id: &types.SubaccountId{ - Owner: "Bob", - Number: 0, - }, + Id: &constants.Bob_Num0, AssetPositions: []*types.AssetPosition{ { AssetId: uint32(0), @@ -3697,10 +3688,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { }, additionalTestSubaccounts: []types.Subaccount{ { - Id: &types.SubaccountId{ - Owner: "Bob", - Number: 0, - }, + Id: &constants.Bob_Num0, AssetPositions: []*types.AssetPosition{ { AssetId: uint32(0), @@ -3772,10 +3760,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { }, additionalTestSubaccounts: []types.Subaccount{ { - Id: &types.SubaccountId{ - Owner: "Bob", - Number: 0, - }, + Id: &constants.Bob_Num0, AssetPositions: []*types.AssetPosition{ { AssetId: uint32(0), diff --git a/protocol/x/subaccounts/types/errors.go b/protocol/x/subaccounts/types/errors.go index 1b5315b187..d27fb280fb 100644 --- a/protocol/x/subaccounts/types/errors.go +++ b/protocol/x/subaccounts/types/errors.go @@ -13,8 +13,10 @@ const ( "exactly 2 updates, got settledUpdates: %+v" ErrMatchUpdatesMustUpdateOnePerp = "internalCanUpdateSubaccounts: MATCH subaccount updates must each have " + "exactly 1 PerpetualUpdate, got settledUpdates: %+v" - ErrMatchUpdatesMustBeSamePerpId = "internalCanUpdateSubaccounts: MATCH subaccount updates must consists two " + + ErrMatchUpdatesMustBeSamePerpId = "internalCanUpdateSubaccounts: MATCH subaccount updates must consists of two " + "updates on same perpetual Id, got settledUpdates: %+v" + ErrMatchUpdatesInvalidSize = "internalCanUpdateSubaccounts: MATCH subaccount updates must consists of two " + + "updates of equal absolute base quantums and opposite sign: %+v" ) // x/subaccounts module sentinel errors From 95fef0c99026ec177e1bc87f92adf0475ba6b50c Mon Sep 17 00:00:00 2001 From: Teddy Ding Date: Thu, 21 Mar 2024 16:20:26 -0400 Subject: [PATCH 4/8] fix test --- .../x/subaccounts/keeper/subaccount_test.go | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/protocol/x/subaccounts/keeper/subaccount_test.go b/protocol/x/subaccounts/keeper/subaccount_test.go index 10cf995a33..3cdf6f6535 100644 --- a/protocol/x/subaccounts/keeper/subaccount_test.go +++ b/protocol/x/subaccounts/keeper/subaccount_test.go @@ -3504,10 +3504,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { BigQuantumsDelta: big.NewInt(-4_500_000_000_000), // -4,500,000 USDC }, }, - SubaccountId: types.SubaccountId{ - Owner: "Bob", - Number: 0, - }, + SubaccountId: constants.Bob_Num0, }, { PerpetualUpdates: []types.PerpetualUpdate{ @@ -3575,10 +3572,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { BigQuantumsDelta: big.NewInt(-4_500_000_000_000), // -4,500,000 USDC }, }, - SubaccountId: types.SubaccountId{ - Owner: "Bob", - Number: 0, - }, + SubaccountId: constants.Bob_Num0, }, { PerpetualUpdates: []types.PerpetualUpdate{ @@ -3648,10 +3642,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { BigQuantumsDelta: big.NewInt(-4_500_000_000_000), // -4,500,000 USDC }, }, - SubaccountId: types.SubaccountId{ - Owner: "Bob", - Number: 0, - }, + SubaccountId: constants.Bob_Num0, }, { PerpetualUpdates: []types.PerpetualUpdate{ @@ -3721,10 +3712,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { BigQuantumsDelta: big.NewInt(-4_500_000_000_000), // -4,500,000 USDC }, }, - SubaccountId: types.SubaccountId{ - Owner: "Bob", - Number: 0, - }, + SubaccountId: constants.Bob_Num0, }, { PerpetualUpdates: []types.PerpetualUpdate{ @@ -3791,10 +3779,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { BigQuantumsDelta: big.NewInt(-4_500_000_000_000), // -4,500,000 USDC }, }, - SubaccountId: types.SubaccountId{ - Owner: "Bob", - Number: 0, - }, + SubaccountId: constants.Bob_Num0, }, { PerpetualUpdates: []types.PerpetualUpdate{ From d33d76647d7a5704ef5ff3e6beba237b7b8e2369 Mon Sep 17 00:00:00 2001 From: Teddy Ding Date: Thu, 21 Mar 2024 22:14:30 -0400 Subject: [PATCH 5/8] fix clob keeper unit test (OI setup) --- .../process_operations_liquidations_test.go | 154 ++++++++--------- .../process_operations_long_term_test.go | 44 ++--- ...ess_operations_stateful_validation_test.go | 68 ++++---- .../x/clob/keeper/process_operations_test.go | 160 +++++++++--------- 4 files changed, 217 insertions(+), 209 deletions(-) diff --git a/protocol/x/clob/keeper/process_operations_liquidations_test.go b/protocol/x/clob/keeper/process_operations_liquidations_test.go index b73dac4170..e4e873458b 100644 --- a/protocol/x/clob/keeper/process_operations_liquidations_test.go +++ b/protocol/x/clob/keeper/process_operations_liquidations_test.go @@ -31,9 +31,9 @@ import ( func TestProcessProposerMatches_Liquidation_Undercollateralized_Determinism(t *testing.T) { // TODO(DEC-908): Set up correct `bankKeeper` mock to verify fee transfer. tc := processProposerOperationsTestCase{ - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, - &constants.EthUsd_20PercentInitial_10PercentMaintenance, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, + constants.EthUsd_20PercentInitial_10PercentMaintenance, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -166,7 +166,7 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { blockHeight := uint32(5) tests := map[string]processProposerOperationsTestCase{ "Liquidation succeeds no fills": { - perpetuals: []*perptypes.Perpetual{&constants.BtcUsd_100PercentMarginRequirement}, + perpetuals: []perptypes.Perpetual{constants.BtcUsd_100PercentMarginRequirement}, subaccounts: []satypes.Subaccount{}, perpetualFeeParams: &constants.PerpetualFeeParams, setupMockBankKeeper: func(bk *mocks.BankKeeper) {}, @@ -180,8 +180,8 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { expectedSubaccountLiquidationInfo: map[satypes.SubaccountId]types.SubaccountLiquidationInfo{}, }, "Liquidation succeeds when order is completely filled": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -258,8 +258,8 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { }, }, "Liquidation succeeds with negative insurance fund delta when order is completely filled": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_50499USD, @@ -344,8 +344,8 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { }, }, "Liquidation succeeds with multiple partial fills": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -442,8 +442,8 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { }, }, "Liquidation succeeds with multiple partial fills - negative insurance fund delta": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_50499USD, @@ -547,8 +547,8 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { }, }, "Liquidation succeeds with both positive and negative insurance fund delta": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_50499USD, @@ -651,8 +651,8 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { }, }, "Insurance fund delta calculation accounts for state changes from previous fills": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_50499USD, @@ -764,8 +764,8 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { }, }, "Liquidation succeeds if matches does not exceed the order quantums when considering state fill amounts": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -863,8 +863,8 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { }, }, "Liquidation succeeds with position size smaller than clobPair.StepBaseQuantums": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1066,8 +1066,8 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { func TestProcessProposerMatches_Liquidation_Failure(t *testing.T) { tests := map[string]processProposerOperationsTestCase{ "Liquidation returns error if order quantums is not divisible by StepBaseQuantums": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1106,8 +1106,8 @@ func TestProcessProposerMatches_Liquidation_Failure(t *testing.T) { expectedError: errors.New("Order Quantums 9 must be a multiple of the ClobPair's StepBaseQuantums"), }, "Liquidation returns error if fillAmount is not divisible by StepBaseQuantums": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1146,8 +1146,8 @@ func TestProcessProposerMatches_Liquidation_Failure(t *testing.T) { expectedError: types.ErrFillAmountNotDivisibleByStepSize, }, "Liquidation returns error if collateralization check fails with non-success": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -1204,8 +1204,8 @@ func TestProcessProposerMatches_Liquidation_Failure(t *testing.T) { ), }, "Liquidation fails if matches exceed the order quantums when considering state fill amounts": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1250,8 +1250,8 @@ func TestProcessProposerMatches_Liquidation_Failure(t *testing.T) { ), }, "Returns error when order filled, subaccounts updated, but transfer to fee module acc failed": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1409,8 +1409,8 @@ func TestProcessProposerMatches_Liquidation_Failure(t *testing.T) { func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { tests := map[string]processProposerOperationsTestCase{ "Stateful order validation: subaccount is not liquidatable": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -1443,8 +1443,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: types.ErrSubaccountNotLiquidatable, }, "Stateful order validation: invalid clob": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1477,9 +1477,9 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: types.ErrInvalidClob, }, "Stateful order validation: subaccount has no open position for perpetual id": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, - &constants.EthUsd_20PercentInitial_10PercentMaintenance, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, + constants.EthUsd_20PercentInitial_10PercentMaintenance, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1516,8 +1516,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: types.ErrNoPerpetualPositionsToLiquidate, }, "Stateful order validation: size of liquidation order exceeds position size": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1550,8 +1550,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: types.ErrInvalidLiquidationOrderTotalSize, }, "Stateful order validation: liquidation order is on the wrong side": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1584,9 +1584,9 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: types.ErrInvalidLiquidationOrderSide, }, "Stateful match validation: clob pair and perpetual ids do not match": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, - &constants.EthUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, + constants.EthUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1630,8 +1630,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: types.ErrClobPairAndPerpetualDoNotMatch, }, "Stateful match validation: fails if collateralization check does not succeed": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -1690,8 +1690,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { ), }, "Stateless match validation: self trade": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1724,8 +1724,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: errors.New("Match constitutes a self-trade"), }, "Stateless match validation: fillAmount must be greater than 0": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1765,9 +1765,9 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: types.ErrFillAmountIsZero, }, "Stateless match validation: clobPairIds do not match": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, - &constants.EthUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, + constants.EthUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1811,8 +1811,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: errors.New("ClobPairIds do not match"), }, "Stateless match validation: maker and taker on the same side": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, // Buy to cover short position. @@ -1855,8 +1855,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: errors.New("Orders are not on opposing sides of the book in match"), }, "Stateless match validation: liquidation buy order doesn't cross with maker sell order": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -1900,8 +1900,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: errors.New("Orders do not cross in match"), }, "Stateless match validation: liquidation sell order doesn't cross with maker buy order": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -1945,8 +1945,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: errors.New("Orders do not cross in match"), }, "Stateless match validation: minimum initial order quantums exceeds fill amount": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -2053,8 +2053,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { // expectedError: types.ErrLiquidationOrderSizeSmallerThanMin, // }, "Subaccount block limit: fails when trying to liquidate the same perpetual id": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -2102,8 +2102,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: types.ErrSubaccountHasLiquidatedPerpetual, }, "Subaccount block limit: fails when liquidation exceeds subaccount notional amount limit": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -2137,8 +2137,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: types.ErrInvalidLiquidationOrderTotalSize, }, "Subaccount block limit: fails when a single liquidation fill exceeds max insurance lost block limit": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_50499USD, @@ -2183,8 +2183,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: types.ErrLiquidationExceedsSubaccountMaxInsuranceLost, }, "Subaccount block limit: fails when insurance lost from multiple liquidation fills exceed block limit": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_50499USD, @@ -2236,8 +2236,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { expectedError: types.ErrLiquidationExceedsSubaccountMaxInsuranceLost, }, "Liquidation checks insurance fund delta for individual fills and not the entire liquidation order": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_50499USD, @@ -2323,8 +2323,8 @@ func TestProcessProposerMatches_Liquidation_Validation_Failure(t *testing.T) { func TestValidateProposerMatches_InsuranceFund(t *testing.T) { tests := map[string]processProposerOperationsTestCase{ "Fails when insurance fund is empty": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_50499USD, @@ -2360,8 +2360,8 @@ func TestValidateProposerMatches_InsuranceFund(t *testing.T) { expectedError: types.ErrInsuranceFundHasInsufficientFunds, }, "Fails when insurance fund is non empty but does not have enough to cover liquidation": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_50499USD, @@ -2397,8 +2397,8 @@ func TestValidateProposerMatches_InsuranceFund(t *testing.T) { expectedError: types.ErrInsuranceFundHasInsufficientFunds, }, "Succeeds when insurance fund has enough balance": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_50499USD, diff --git a/protocol/x/clob/keeper/process_operations_long_term_test.go b/protocol/x/clob/keeper/process_operations_long_term_test.go index f2ecafc3a5..ff7baa9644 100644 --- a/protocol/x/clob/keeper/process_operations_long_term_test.go +++ b/protocol/x/clob/keeper/process_operations_long_term_test.go @@ -24,8 +24,8 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { blockHeight := uint32(5) tests := map[string]processProposerOperationsTestCase{ "Succeeds with new maker Long-Term order": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -97,8 +97,8 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { }, }, "Succeeds with new taker Long-Term order": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -170,8 +170,8 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { }, }, "Succeeds with existing maker Long-Term order": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -243,8 +243,8 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { }, }, "Succeeds with existing taker Long-Term order": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -316,8 +316,8 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { }, }, "Succeeds with new maker and taker Long-Term orders completely filled": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -389,8 +389,8 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { }, }, "Succeeds with new maker and taker Long-Term orders partially filled": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -468,8 +468,8 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { }, }, "Succeeds with Long-Term order and multiple fills": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -555,8 +555,8 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { }, }, "Succeeds with new maker Long-Term order in liquidation match": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -643,8 +643,8 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { }, }, "Succeeds with existing maker Long-Term order in liquidation match": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -731,8 +731,8 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { }, }, "Succeeds with maker Long-Term order when considering state fill amount": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -822,8 +822,8 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { }, }, "Succeeds with taker Long-Term order when considering state fill amount": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, diff --git a/protocol/x/clob/keeper/process_operations_stateful_validation_test.go b/protocol/x/clob/keeper/process_operations_stateful_validation_test.go index f944523dc0..6b608b68c0 100644 --- a/protocol/x/clob/keeper/process_operations_stateful_validation_test.go +++ b/protocol/x/clob/keeper/process_operations_stateful_validation_test.go @@ -26,8 +26,8 @@ import ( func TestProcessProposerMatches_LongTerm_StatefulValidation_Failure(t *testing.T) { tests := map[string]processProposerOperationsTestCase{ `Stateful order validation: referenced maker order does not exist in state`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -58,8 +58,8 @@ func TestProcessProposerMatches_LongTerm_StatefulValidation_Failure(t *testing.T ), }, `Stateful order validation: referenced taker order does not exist in state`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -90,8 +90,8 @@ func TestProcessProposerMatches_LongTerm_StatefulValidation_Failure(t *testing.T ), }, `Stateful order validation: referenced maker order in liquidation match does not exist in state`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -126,8 +126,8 @@ func TestProcessProposerMatches_LongTerm_StatefulValidation_Failure(t *testing.T ), }, `Stateful order validation: referenced long-term order is on the wrong side`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -162,8 +162,8 @@ func TestProcessProposerMatches_LongTerm_StatefulValidation_Failure(t *testing.T expectedError: errors.New("Orders are not on opposing sides of the book in match"), }, `Stateful match validation: taker order cannot be post only`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -200,8 +200,8 @@ func TestProcessProposerMatches_LongTerm_StatefulValidation_Failure(t *testing.T ), }, `Stateful match validation: maker order cannot be FOK`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -234,8 +234,8 @@ func TestProcessProposerMatches_LongTerm_StatefulValidation_Failure(t *testing.T expectedError: errors.New("IOC / FOK order cannot be matched as a maker order"), }, `Stateful match validation: maker order cannot be IOC`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -268,9 +268,9 @@ func TestProcessProposerMatches_LongTerm_StatefulValidation_Failure(t *testing.T expectedError: errors.New("IOC / FOK order cannot be matched as a maker order"), }, `Stateful order validation: referenced long-term order is for the wrong clob pair`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, - &constants.EthUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, + constants.EthUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -323,8 +323,8 @@ func TestProcessProposerMatches_LongTerm_StatefulValidation_Failure(t *testing.T expectedError: errors.New("ClobPairIds do not match in match"), }, "Fails with Long-Term order when considering state fill amount": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -393,8 +393,8 @@ func TestProcessProposerMatches_LongTerm_StatefulValidation_Failure(t *testing.T func TestProcessProposerMatches_Conditional_Validation_Failure(t *testing.T) { tests := map[string]processProposerOperationsTestCase{ `Stateful order validation: referenced maker order does not exist in state`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -425,8 +425,8 @@ func TestProcessProposerMatches_Conditional_Validation_Failure(t *testing.T) { ), }, `Stateful order validation: referenced taker order does not exist in state`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -457,8 +457,8 @@ func TestProcessProposerMatches_Conditional_Validation_Failure(t *testing.T) { ), }, `Stateful order validation: referenced maker order in liquidation match does not exist in state`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short_54999USD, @@ -493,8 +493,8 @@ func TestProcessProposerMatches_Conditional_Validation_Failure(t *testing.T) { ), }, `Stateful order validation: referenced maker order exist in state but is untriggered`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -528,8 +528,8 @@ func TestProcessProposerMatches_Conditional_Validation_Failure(t *testing.T) { ), }, `Stateful order validation: referenced conditional order is on the wrong side`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -564,9 +564,9 @@ func TestProcessProposerMatches_Conditional_Validation_Failure(t *testing.T) { expectedError: errors.New("Orders are not on opposing sides of the book in match"), }, `Stateful order validation: referenced conditional order is for the wrong clob pair`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, - &constants.EthUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, + constants.EthUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, @@ -619,8 +619,8 @@ func TestProcessProposerMatches_Conditional_Validation_Failure(t *testing.T) { expectedError: errors.New("ClobPairIds do not match in match"), }, "Fails with conditional order when considering state fill amount": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, subaccounts: []satypes.Subaccount{ constants.Carl_Num0_1BTC_Short, diff --git a/protocol/x/clob/keeper/process_operations_test.go b/protocol/x/clob/keeper/process_operations_test.go index df6af12351..69d6a1d42d 100644 --- a/protocol/x/clob/keeper/process_operations_test.go +++ b/protocol/x/clob/keeper/process_operations_test.go @@ -20,6 +20,7 @@ import ( clobtest "github.com/dydxprotocol/v4-chain/protocol/testutil/clob" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" + perptest "github.com/dydxprotocol/v4-chain/protocol/testutil/perpetuals" blocktimetypes "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/types" "github.com/dydxprotocol/v4-chain/protocol/x/clob/memclob" "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" @@ -41,7 +42,7 @@ type MatchWithOrdersForTesting struct { type processProposerOperationsTestCase struct { // State - perpetuals []*perptypes.Perpetual + perpetuals []perptypes.Perpetual perpetualFeeParams *feetierstypes.PerpetualFeeParams clobPairs []types.ClobPair subaccounts []satypes.Subaccount @@ -75,7 +76,7 @@ func TestProcessProposerOperations(t *testing.T) { blockHeight := uint32(5) tests := map[string]processProposerOperationsTestCase{ "Succeeds no operations": { - perpetuals: []*perptypes.Perpetual{}, + perpetuals: []perptypes.Perpetual{}, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{}, subaccounts: []satypes.Subaccount{}, @@ -87,8 +88,8 @@ func TestProcessProposerOperations(t *testing.T) { }, }, "Succeeds no operations with previous stateful orders": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -106,8 +107,8 @@ func TestProcessProposerOperations(t *testing.T) { }, }, "Succeeds with singular match of a short term maker and short term taker": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -230,8 +231,8 @@ func TestProcessProposerOperations(t *testing.T) { }, }, "Succeeds with maker rebate": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParamsMakerRebate, clobPairs: []types.ClobPair{ @@ -354,8 +355,8 @@ func TestProcessProposerOperations(t *testing.T) { }, }, "Succeeds with singular match of a preexisting maker and short term taker": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -465,8 +466,8 @@ func TestProcessProposerOperations(t *testing.T) { }, }, "Succeeds with singular match of a preexisting maker and newly placed long term taker": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -557,8 +558,8 @@ func TestProcessProposerOperations(t *testing.T) { }, }, "preexisting stateful maker order partially matches with 2 short term taker orders": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -731,8 +732,8 @@ func TestProcessProposerOperations(t *testing.T) { // $49,999 is transferred to Dave and Carl's $1 is paid to the insurance fund, leaving him // with nothing. "Succeeds with liquidation order": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_20PercentInitial_10PercentMaintenance, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -797,8 +798,8 @@ func TestProcessProposerOperations(t *testing.T) { // is negative. // Deleveraging happens at the bankruptcy price ($50,499) so Dave ends up with all of Carl's money. "Succeeds with deleveraging with no liquidation order": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_20PercentInitial_10PercentMaintenance, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -847,8 +848,8 @@ func TestProcessProposerOperations(t *testing.T) { // In this example, the liquidation and deleveraging // both happen at bankruptcy price resulting in all of Carl's funds being transferred to Dave. "Succeeds with deleveraging and partially filled liquidation": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_20PercentInitial_10PercentMaintenance, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -924,8 +925,8 @@ func TestProcessProposerOperations(t *testing.T) { }, "Zero-fill deleveraging succeeds when the account is negative TNC and updates the last negative TNC subaccount " + "seen block number in state": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_20PercentInitial_10PercentMaintenance, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -961,8 +962,8 @@ func TestProcessProposerOperations(t *testing.T) { }, "Zero-fill deleveraging succeeds when the account is negative TNC and has a position in final settlement" + " market. It updates the last negative TNC subaccount seen block number in state": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1001,8 +1002,8 @@ func TestProcessProposerOperations(t *testing.T) { }, "Zero-fill deleveraging succeeds when there's multiple zero-fill deleveraging events for the same subaccount " + "and perpetual ID": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_20PercentInitial_10PercentMaintenance, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1044,8 +1045,8 @@ func TestProcessProposerOperations(t *testing.T) { expectedNegativeTncSubaccountSeen: true, }, "Zero-fill deleverage succeeds after the same subaccount is partially deleveraged": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_20PercentInitial_10PercentMaintenance, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1106,8 +1107,8 @@ func TestProcessProposerOperations(t *testing.T) { expectedNegativeTncSubaccountSeen: true, }, "Succeeds order removal operations with previous stateful orders": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1139,8 +1140,8 @@ func TestProcessProposerOperations(t *testing.T) { }, }, "Fails when attempting to match order with invalid order side": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1214,8 +1215,8 @@ func TestProcessProposerOperations(t *testing.T) { // This test proposes an invalid perpetual deleveraging liquidation match operation. The // subaccount is not liquidatable, so the match operation should be rejected. "Fails with deleveraging match for non-liquidatable subaccount": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_20PercentInitial_10PercentMaintenance, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1252,8 +1253,8 @@ func TestProcessProposerOperations(t *testing.T) { // This test proposes an invalid perpetual deleveraging liquidation match operation. The // subaccount has zero TNC, so the deleveraging operation should be rejected. "Fails with deleveraging match for subaccount with zero TNC": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_20PercentInitial_10PercentMaintenance, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1291,8 +1292,8 @@ func TestProcessProposerOperations(t *testing.T) { expectedError: types.ErrInvalidDeleveragedSubaccount, }, "Conditional: succeeds with singular match of a triggered conditional order": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1385,8 +1386,8 @@ func TestProcessProposerOperations(t *testing.T) { }, }, "Conditional: panics with a non-existent conditional order": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1439,8 +1440,8 @@ func TestProcessProposerOperations(t *testing.T) { ), }, "Conditional: panics with an untriggered conditional order": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1494,8 +1495,8 @@ func TestProcessProposerOperations(t *testing.T) { ), }, "Fails with clob pair not found": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, rawOperations: []types.OperationRaw{ @@ -1512,8 +1513,8 @@ func TestProcessProposerOperations(t *testing.T) { expectedError: types.ErrInvalidClob, }, "Panics with unsupported clob pair status": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, rawOperations: []types.OperationRaw{ @@ -1537,8 +1538,8 @@ func TestProcessProposerOperations(t *testing.T) { expectedPanics: "validateInternalOperationAgainstClobPairStatus: ClobPair's status is not supported", }, "Returns error if zero-fill deleveraging operation proposed for non-negative TNC subaccount in final settlement": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1573,8 +1574,8 @@ func TestProcessProposerOperations(t *testing.T) { expectedError: types.ErrZeroFillDeleveragingForNonNegativeTncSubaccount, }, "Fails with clob match for market in initializing mode": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1594,8 +1595,8 @@ func TestProcessProposerOperations(t *testing.T) { expectedError: types.ErrOperationConflictsWithClobPairStatus, }, "Fails with short term order placement for market in initializing mode": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1609,8 +1610,8 @@ func TestProcessProposerOperations(t *testing.T) { expectedError: types.ErrOperationConflictsWithClobPairStatus, }, "Fails with order removal for market in initializing mode": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1628,8 +1629,8 @@ func TestProcessProposerOperations(t *testing.T) { expectedError: types.ErrOperationConflictsWithClobPairStatus, }, "Fails with order removal reason fully filled": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1647,8 +1648,8 @@ func TestProcessProposerOperations(t *testing.T) { expectedError: types.ErrInvalidOrderRemoval, }, "Fails with order removal for market in final settlement": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1663,8 +1664,8 @@ func TestProcessProposerOperations(t *testing.T) { expectedError: types.ErrOperationConflictsWithClobPairStatus, }, "Fails with short-term order placement for market in final settlement": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1678,8 +1679,8 @@ func TestProcessProposerOperations(t *testing.T) { expectedError: types.ErrOperationConflictsWithClobPairStatus, }, "Fails with ClobMatch_MatchOrders for market in final settlement": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1701,8 +1702,8 @@ func TestProcessProposerOperations(t *testing.T) { // Liquidations are disallowed for markets in final settlement because they may result // in a position increasing in size. This is not allowed for markets in final settlement. "Fails with ClobMatch_MatchPerpetualLiquidation for market in final settlement": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1730,8 +1731,8 @@ func TestProcessProposerOperations(t *testing.T) { // Deleveraging is allowed for markets in final settlement to close out all open positions. A deleveraging // event with IsFinalSettlement set to false represents a negative TNC subaccount in the market getting deleveraged. "Succeeds with ClobMatch_MatchPerpetualDeleveraging, IsFinalSettlement is false for market in final settlement": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1775,8 +1776,8 @@ func TestProcessProposerOperations(t *testing.T) { // event with IsFinalSettlement set to true represents a non-negative TNC subaccount having its position closed // at the oracle price against other subaccounts with open positions on the opposing side of the book. "Succeeds with ClobMatch_MatchPerpetualDeleveraging, IsFinalSettlement is true for market in final settlement": { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1818,8 +1819,8 @@ func TestProcessProposerOperations(t *testing.T) { // shouldFinalSettlePosition, but the IsFinalSettlement flag is set to true. `Fails with ClobMatch_MatchPerpetualDeleveraging for negative TNC subaccount, IsFinalSettlement is true for market not in final settlement`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1855,8 +1856,8 @@ func TestProcessProposerOperations(t *testing.T) { // using the bankruptcy price. `Fails with ClobMatch_MatchPerpetualDeleveraging for negative TNC subaccount, IsFinalSettlement is true for market in final settlement`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1894,8 +1895,8 @@ func TestProcessProposerOperations(t *testing.T) { // a non-negative TNC subaccount in a market not in final settlement. `Fails with ClobMatch_MatchPerpetualDeleveraging for non-negative TNC subaccount, IsFinalSettlement is true for market not in final settlement`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -1927,8 +1928,8 @@ func TestProcessProposerOperations(t *testing.T) { }, `Fails with ClobMatch_MatchPerpetualDeleveraging for non-negative TNC subaccount, IsFinalSettlement is false for market in final settlement`: { - perpetuals: []*perptypes.Perpetual{ - &constants.BtcUsd_100PercentMarginRequirement, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, }, perpetualFeeParams: &constants.PerpetualFeeParams, clobPairs: []types.ClobPair{ @@ -2234,6 +2235,13 @@ func setupProcessProposerOperationsTestCase( require.NoError(t, err) } + perptest.SetUpDefaultPerpOIsForTest( + t, + ks.Ctx, + ks.PerpetualsKeeper, + tc.perpetuals, + ) + // Create all subaccounts. for _, subaccount := range tc.subaccounts { ks.SubaccountsKeeper.SetSubaccount(ctx, subaccount) From ecd802afb46ce15dc46d4dcf0d1210dfe74cf26c Mon Sep 17 00:00:00 2001 From: Teddy Ding Date: Thu, 21 Mar 2024 23:33:02 -0400 Subject: [PATCH 6/8] fix liquidation config e2e --- protocol/testutil/constants/perpetuals.go | 13 +++++++ .../clob/e2e/liquidation_deleveraging_test.go | 36 +++++++++---------- protocol/x/perpetuals/genesis.go | 18 +++------- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/protocol/testutil/constants/perpetuals.go b/protocol/testutil/constants/perpetuals.go index 1d4a9f06d3..3c6db1e6fa 100644 --- a/protocol/testutil/constants/perpetuals.go +++ b/protocol/testutil/constants/perpetuals.go @@ -264,6 +264,19 @@ var ( FundingIndex: dtypes.ZeroInt(), OpenInterest: dtypes.ZeroInt(), } + BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1 = perptypes.Perpetual{ + Params: perptypes.PerpetualParams{ + Id: 0, + Ticker: "BTC-USD 20/10 margin requirements", + MarketId: uint32(0), + AtomicResolution: int32(-8), + DefaultFundingPpm: int32(0), + LiquidityTier: uint32(3), + MarketType: perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + }, + FundingIndex: dtypes.ZeroInt(), + OpenInterest: dtypes.NewInt(100_000_000), + } BtcUsd_20PercentInitial_10PercentMaintenance_25mmLowerCap_50mmUpperCap = perptypes.Perpetual{ Params: perptypes.PerpetualParams{ Id: 0, diff --git a/protocol/x/clob/e2e/liquidation_deleveraging_test.go b/protocol/x/clob/e2e/liquidation_deleveraging_test.go index 167ff50fd8..0e4103c111 100644 --- a/protocol/x/clob/e2e/liquidation_deleveraging_test.go +++ b/protocol/x/clob/e2e/liquidation_deleveraging_test.go @@ -65,7 +65,7 @@ func TestLiquidationConfig(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -115,7 +115,7 @@ func TestLiquidationConfig(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -163,7 +163,7 @@ func TestLiquidationConfig(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -225,7 +225,7 @@ func TestLiquidationConfig(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -287,7 +287,7 @@ func TestLiquidationConfig(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -353,7 +353,7 @@ func TestLiquidationConfig(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -425,7 +425,7 @@ func TestLiquidationConfig(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -498,7 +498,7 @@ func TestLiquidationConfig(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -670,7 +670,7 @@ func TestPlacePerpetualLiquidation_Deleveraging(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -712,7 +712,7 @@ func TestPlacePerpetualLiquidation_Deleveraging(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -771,7 +771,7 @@ func TestPlacePerpetualLiquidation_Deleveraging(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -812,7 +812,7 @@ func TestPlacePerpetualLiquidation_Deleveraging(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -869,7 +869,7 @@ func TestPlacePerpetualLiquidation_Deleveraging(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -901,7 +901,7 @@ func TestPlacePerpetualLiquidation_Deleveraging(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -957,7 +957,7 @@ func TestPlacePerpetualLiquidation_Deleveraging(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -1022,7 +1022,7 @@ func TestPlacePerpetualLiquidation_Deleveraging(t *testing.T) { liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, @@ -1056,7 +1056,7 @@ func TestPlacePerpetualLiquidation_Deleveraging(t *testing.T) { liquidationConfig: constants.LiquidationsConfig_FillablePrice_Max_Smmr, liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc_Final_Settlement}, @@ -1085,7 +1085,7 @@ func TestPlacePerpetualLiquidation_Deleveraging(t *testing.T) { liquidationConfig: constants.LiquidationsConfig_FillablePrice_Max_Smmr, liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc_Final_Settlement}, diff --git a/protocol/x/perpetuals/genesis.go b/protocol/x/perpetuals/genesis.go index db9650d916..7c9b9d7e59 100644 --- a/protocol/x/perpetuals/genesis.go +++ b/protocol/x/perpetuals/genesis.go @@ -35,22 +35,14 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) } } - // Create all the perpetuals. + // Initialize all the perpetuals. + // Instead of calling `CreatePerpetual`, we call `SetPerpetual` directly to + // to allow initializing non-zero open interest/funding index at genesis. for _, elem := range genState.Perpetuals { - _, err := k.CreatePerpetual( + k.SetPerpetual( ctx, - elem.Params.Id, - elem.Params.Ticker, - elem.Params.MarketId, - elem.Params.AtomicResolution, - elem.Params.DefaultFundingPpm, - elem.Params.LiquidityTier, - elem.Params.MarketType, + elem, ) - - if err != nil { - panic(err) - } } } From a458b7eee5f154fe6c94e2e833866dca93693bfc Mon Sep 17 00:00:00 2001 From: Teddy Ding Date: Thu, 21 Mar 2024 23:48:11 -0400 Subject: [PATCH 7/8] fix e2e test --- protocol/x/clob/e2e/withdrawal_gating_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/x/clob/e2e/withdrawal_gating_test.go b/protocol/x/clob/e2e/withdrawal_gating_test.go index 7e5d34cce3..a69101ff45 100644 --- a/protocol/x/clob/e2e/withdrawal_gating_test.go +++ b/protocol/x/clob/e2e/withdrawal_gating_test.go @@ -73,7 +73,7 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, transferOrWithdrawSubaccount: constants.Dave_Num1, @@ -112,7 +112,7 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) liquidityTiers: constants.LiquidityTiers, perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, }, clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, transferOrWithdrawSubaccount: constants.Dave_Num1, From b4ad946c01daca58a51d321fa7e5a6b8f00638e7 Mon Sep 17 00:00:00 2001 From: Teddy Ding Date: Fri, 22 Mar 2024 12:16:37 -0400 Subject: [PATCH 8/8] fix e2e wind_down_market --- protocol/testing/e2e/gov/wind_down_market_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/testing/e2e/gov/wind_down_market_test.go b/protocol/testing/e2e/gov/wind_down_market_test.go index b994383b2b..afdfeafebd 100644 --- a/protocol/testing/e2e/gov/wind_down_market_test.go +++ b/protocol/testing/e2e/gov/wind_down_market_test.go @@ -146,7 +146,7 @@ func TestWindDownMarketProposal(t *testing.T) { genesisState.Params = constants.PerpetualsGenesisParams genesisState.LiquidityTiers = constants.LiquidityTiers genesisState.Perpetuals = []perptypes.Perpetual{ - constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, } }, )