From 6b03c0a1582bc79f8750aa69a11aa564ef9bc24b Mon Sep 17 00:00:00 2001 From: Teddy Ding Date: Tue, 19 Mar 2024 15:31:48 -0400 Subject: [PATCH] internalCanUpdate uses OIMF; need to fix e2e --- protocol/mocks/PerpetualsKeeper.go | 85 ++- protocol/testutil/constants/perpetuals.go | 40 ++ protocol/testutil/perpetuals/perpetuals.go | 28 + 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 + .../subaccounts/keeper/isolated_subaccount.go | 4 +- protocol/x/subaccounts/keeper/oimf.go | 89 ++++ protocol/x/subaccounts/keeper/oimf_test.go | 302 +++++++++++ protocol/x/subaccounts/keeper/subaccount.go | 74 ++- .../x/subaccounts/keeper/subaccount_helper.go | 8 +- .../x/subaccounts/keeper/subaccount_test.go | 494 +++++++++++++++++- protocol/x/subaccounts/keeper/update.go | 4 +- protocol/x/subaccounts/types/errors.go | 26 +- .../x/subaccounts/types/expected_keepers.go | 1 + protocol/x/subaccounts/types/update.go | 14 +- 19 files changed, 1232 insertions(+), 58 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 a82605b3544..6be283f32d2 100644 --- a/protocol/mocks/PerpetualsKeeper.go +++ b/protocol/mocks/PerpetualsKeeper.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery v2.40.1. DO NOT EDIT. package mocks @@ -20,6 +20,10 @@ type PerpetualsKeeper struct { func (_m *PerpetualsKeeper) AddPremiumVotes(ctx types.Context, votes []perpetualstypes.FundingPremium) error { ret := _m.Called(ctx, votes) + if len(ret) == 0 { + panic("no return value specified for AddPremiumVotes") + } + var r0 error if rf, ok := ret.Get(0).(func(types.Context, []perpetualstypes.FundingPremium) error); ok { r0 = rf(ctx, votes) @@ -34,6 +38,10 @@ func (_m *PerpetualsKeeper) AddPremiumVotes(ctx types.Context, votes []perpetual func (_m *PerpetualsKeeper) CreatePerpetual(ctx types.Context, id uint32, ticker string, marketId uint32, atomicResolution int32, defaultFundingPpm int32, liquidityTier uint32, marketType perpetualstypes.PerpetualMarketType) (perpetualstypes.Perpetual, error) { ret := _m.Called(ctx, id, ticker, marketId, atomicResolution, defaultFundingPpm, liquidityTier, marketType) + if len(ret) == 0 { + panic("no return value specified for CreatePerpetual") + } + var r0 perpetualstypes.Perpetual var r1 error if rf, ok := ret.Get(0).(func(types.Context, uint32, string, uint32, int32, int32, uint32, perpetualstypes.PerpetualMarketType) (perpetualstypes.Perpetual, error)); ok { @@ -58,6 +66,10 @@ func (_m *PerpetualsKeeper) CreatePerpetual(ctx types.Context, id uint32, ticker func (_m *PerpetualsKeeper) GetAddPremiumVotes(ctx types.Context) *perpetualstypes.MsgAddPremiumVotes { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetAddPremiumVotes") + } + var r0 *perpetualstypes.MsgAddPremiumVotes if rf, ok := ret.Get(0).(func(types.Context) *perpetualstypes.MsgAddPremiumVotes); ok { r0 = rf(ctx) @@ -74,6 +86,10 @@ func (_m *PerpetualsKeeper) GetAddPremiumVotes(ctx types.Context) *perpetualstyp func (_m *PerpetualsKeeper) GetAllPerpetuals(ctx types.Context) []perpetualstypes.Perpetual { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetAllPerpetuals") + } + var r0 []perpetualstypes.Perpetual if rf, ok := ret.Get(0).(func(types.Context) []perpetualstypes.Perpetual); ok { r0 = rf(ctx) @@ -90,6 +106,10 @@ func (_m *PerpetualsKeeper) GetAllPerpetuals(ctx types.Context) []perpetualstype func (_m *PerpetualsKeeper) GetMarginRequirements(ctx types.Context, id uint32, bigQuantums *big.Int) (*big.Int, *big.Int, error) { ret := _m.Called(ctx, id, bigQuantums) + if len(ret) == 0 { + panic("no return value specified for GetMarginRequirements") + } + var r0 *big.Int var r1 *big.Int var r2 error @@ -125,6 +145,10 @@ func (_m *PerpetualsKeeper) GetMarginRequirements(ctx types.Context, id uint32, func (_m *PerpetualsKeeper) GetNetCollateral(ctx types.Context, id uint32, bigQuantums *big.Int) (*big.Int, error) { ret := _m.Called(ctx, id, bigQuantums) + if len(ret) == 0 { + panic("no return value specified for GetNetCollateral") + } + var r0 *big.Int var r1 error if rf, ok := ret.Get(0).(func(types.Context, uint32, *big.Int) (*big.Int, error)); ok { @@ -151,6 +175,10 @@ func (_m *PerpetualsKeeper) GetNetCollateral(ctx types.Context, id uint32, bigQu func (_m *PerpetualsKeeper) GetNetNotional(ctx types.Context, id uint32, bigQuantums *big.Int) (*big.Int, error) { ret := _m.Called(ctx, id, bigQuantums) + if len(ret) == 0 { + panic("no return value specified for GetNetNotional") + } + var r0 *big.Int var r1 error if rf, ok := ret.Get(0).(func(types.Context, uint32, *big.Int) (*big.Int, error)); ok { @@ -177,6 +205,10 @@ func (_m *PerpetualsKeeper) GetNetNotional(ctx types.Context, id uint32, bigQuan func (_m *PerpetualsKeeper) GetNotionalInBaseQuantums(ctx types.Context, id uint32, bigQuoteQuantums *big.Int) (*big.Int, error) { ret := _m.Called(ctx, id, bigQuoteQuantums) + if len(ret) == 0 { + panic("no return value specified for GetNotionalInBaseQuantums") + } + var r0 *big.Int var r1 error if rf, ok := ret.Get(0).(func(types.Context, uint32, *big.Int) (*big.Int, error)); ok { @@ -203,6 +235,10 @@ func (_m *PerpetualsKeeper) GetNotionalInBaseQuantums(ctx types.Context, id uint func (_m *PerpetualsKeeper) HasAuthority(authority string) bool { ret := _m.Called(authority) + if len(ret) == 0 { + panic("no return value specified for HasAuthority") + } + var r0 bool if rf, ok := ret.Get(0).(func(string) bool); ok { r0 = rf(authority) @@ -223,10 +259,32 @@ 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) + if len(ret) == 0 { + panic("no return value specified for ModifyPerpetual") + } + var r0 perpetualstypes.Perpetual var r1 error if rf, ok := ret.Get(0).(func(types.Context, uint32, string, uint32, int32, uint32) (perpetualstypes.Perpetual, error)); ok { @@ -251,6 +309,10 @@ func (_m *PerpetualsKeeper) ModifyPerpetual(ctx types.Context, id uint32, ticker func (_m *PerpetualsKeeper) PerformStatefulPremiumVotesValidation(ctx types.Context, msg *perpetualstypes.MsgAddPremiumVotes) error { ret := _m.Called(ctx, msg) + if len(ret) == 0 { + panic("no return value specified for PerformStatefulPremiumVotesValidation") + } + var r0 error if rf, ok := ret.Get(0).(func(types.Context, *perpetualstypes.MsgAddPremiumVotes) error); ok { r0 = rf(ctx, msg) @@ -265,6 +327,10 @@ func (_m *PerpetualsKeeper) PerformStatefulPremiumVotesValidation(ctx types.Cont func (_m *PerpetualsKeeper) SetLiquidityTier(ctx types.Context, id uint32, name string, initialMarginPpm uint32, maintenanceFractionPpm uint32, impactNotional uint64, openInterestLowerCap uint64, openInterestUpperCap uint64) (perpetualstypes.LiquidityTier, error) { ret := _m.Called(ctx, id, name, initialMarginPpm, maintenanceFractionPpm, impactNotional, openInterestLowerCap, openInterestUpperCap) + if len(ret) == 0 { + panic("no return value specified for SetLiquidityTier") + } + var r0 perpetualstypes.LiquidityTier var r1 error if rf, ok := ret.Get(0).(func(types.Context, uint32, string, uint32, uint32, uint64, uint64, uint64) (perpetualstypes.LiquidityTier, error)); ok { @@ -289,6 +355,10 @@ func (_m *PerpetualsKeeper) SetLiquidityTier(ctx types.Context, id uint32, name func (_m *PerpetualsKeeper) SetParams(ctx types.Context, params perpetualstypes.Params) error { ret := _m.Called(ctx, params) + if len(ret) == 0 { + panic("no return value specified for SetParams") + } + var r0 error if rf, ok := ret.Get(0).(func(types.Context, perpetualstypes.Params) error); ok { r0 = rf(ctx, params) @@ -303,6 +373,10 @@ func (_m *PerpetualsKeeper) SetParams(ctx types.Context, params perpetualstypes. func (_m *PerpetualsKeeper) SetPerpetualMarketType(ctx types.Context, id uint32, marketType perpetualstypes.PerpetualMarketType) (perpetualstypes.Perpetual, error) { ret := _m.Called(ctx, id, marketType) + if len(ret) == 0 { + panic("no return value specified for SetPerpetualMarketType") + } + var r0 perpetualstypes.Perpetual var r1 error if rf, ok := ret.Get(0).(func(types.Context, uint32, perpetualstypes.PerpetualMarketType) (perpetualstypes.Perpetual, error)); ok { @@ -323,13 +397,12 @@ func (_m *PerpetualsKeeper) SetPerpetualMarketType(ctx types.Context, id uint32, return r0, r1 } -type mockConstructorTestingTNewPerpetualsKeeper interface { +// NewPerpetualsKeeper creates a new instance of PerpetualsKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewPerpetualsKeeper(t interface { mock.TestingT Cleanup(func()) -} - -// NewPerpetualsKeeper creates a new instance of PerpetualsKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewPerpetualsKeeper(t mockConstructorTestingTNewPerpetualsKeeper) *PerpetualsKeeper { +}) *PerpetualsKeeper { mock := &PerpetualsKeeper{} mock.Mock.Test(t) diff --git a/protocol/testutil/constants/perpetuals.go b/protocol/testutil/constants/perpetuals.go index 89f31f62f15..06ec243b6a2 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 dbcce922291..9141a8eb27b 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,26 @@ func abs(n int64) int64 { } return n } + +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 1bfeda123a7..b5d4c6e0cb6 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" @@ -765,6 +766,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, @@ -1212,10 +1220,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, @@ -1229,6 +1238,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) @@ -1423,10 +1439,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, @@ -1440,6 +1457,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) @@ -1585,10 +1609,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, @@ -1602,6 +1627,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 ec916312e10..4858eab74d7 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) } @@ -2045,6 +2061,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) } @@ -2756,6 +2779,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", @@ -2796,7 +2826,7 @@ func TestGetBankruptcyPriceInQuoteQuantums(t *testing.T) { }, }, }, - satypes.Match, + satypes.CollatCheck, ) require.True(t, success) @@ -4694,6 +4724,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 f65aff2fce7..813b1d1f200 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 3bf07957432..20f8daebdbf 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 1603f5d8bfb..bcaad7caff1 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 84cfaacd6e7..81ba665c2b2 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/isolated_subaccount.go b/protocol/x/subaccounts/keeper/isolated_subaccount.go index e5547d3986c..bb664be6e8a 100644 --- a/protocol/x/subaccounts/keeper/isolated_subaccount.go +++ b/protocol/x/subaccounts/keeper/isolated_subaccount.go @@ -22,7 +22,7 @@ import ( // caused a failure, if any. func (k Keeper) checkIsolatedSubaccountConstraints( ctx sdk.Context, - settledUpdates []settledUpdate, + settledUpdates []SettledUpdate, perpetuals []perptypes.Perpetual, ) ( success bool, @@ -63,7 +63,7 @@ func (k Keeper) checkIsolatedSubaccountConstraints( // - a subaccount with no positions cannot be updated to have positions in multiple isolated // perpetuals or a combination of isolated and non-isolated perpetuals func isValidIsolatedPerpetualUpdates( - settledUpdate settledUpdate, + settledUpdate SettledUpdate, perpIdToMarketType map[uint32]perptypes.PerpetualMarketType, ) (types.UpdateResult, error) { // If there are no perpetual updates, then this update does not violate constraints for isolated diff --git a/protocol/x/subaccounts/keeper/oimf.go b/protocol/x/subaccounts/keeper/oimf.go new file mode 100644 index 00000000000..8fe7f18ca3e --- /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 00000000000..27d4c8b56bb --- /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 ca9e978e80f..833d19d0a49 100644 --- a/protocol/x/subaccounts/keeper/subaccount.go +++ b/protocol/x/subaccounts/keeper/subaccount.go @@ -206,12 +206,12 @@ func (k Keeper) getSettledUpdates( updates []types.Update, requireUniqueSubaccount bool, ) ( - settledUpdates []settledUpdate, + settledUpdates []SettledUpdate, subaccountIdToFundingPayments map[types.SubaccountId]map[uint32]dtypes.SerializableInt, err error, ) { var idToSettledSubaccount = make(map[types.SubaccountId]types.Subaccount) - settledUpdates = make([]settledUpdate, len(updates)) + settledUpdates = make([]SettledUpdate, len(updates)) subaccountIdToFundingPayments = make(map[types.SubaccountId]map[uint32]dtypes.SerializableInt) // Iterate over all updates and query the relevant `Subaccounts`. @@ -236,7 +236,7 @@ func (k Keeper) getSettledUpdates( subaccountIdToFundingPayments[u.SubaccountId] = fundingPayments } - settledUpdate := settledUpdate{ + settledUpdate := SettledUpdate{ SettledSubaccount: settledSubaccount, AssetUpdates: u.AssetUpdates, PerpetualUpdates: u.PerpetualUpdates, @@ -528,7 +528,7 @@ func checkPositionUpdatable( // caused a failure, if any. func (k Keeper) internalCanUpdateSubaccounts( ctx sdk.Context, - settledUpdates []settledUpdate, + settledUpdates []SettledUpdate, updateType types.UpdateType, perpetuals []perptypes.Perpetual, ) ( @@ -609,6 +609,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) @@ -631,11 +642,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 } @@ -646,7 +698,7 @@ func (k Keeper) internalCanUpdateSubaccounts( // We must now check if the state transition is valid. if bigNewInitialMargin.Cmp(bigNewNetCollateral) > 0 { // Get the current collateralization and margin requirements without the update applied. - emptyUpdate := settledUpdate{ + emptyUpdate := SettledUpdate{ SettledSubaccount: u.SettledSubaccount, } @@ -785,7 +837,7 @@ func (k Keeper) GetNetCollateralAndMarginRequirements( return nil, nil, nil, err } - settledUpdate := settledUpdate{ + settledUpdate := SettledUpdate{ SettledSubaccount: settledSubaccount, AssetUpdates: update.AssetUpdates, PerpetualUpdates: update.PerpetualUpdates, @@ -810,7 +862,7 @@ func (k Keeper) GetNetCollateralAndMarginRequirements( // If two position updates reference the same position, an error is returned. func (k Keeper) internalGetNetCollateralAndMarginRequirements( ctx sdk.Context, - settledUpdate settledUpdate, + settledUpdate SettledUpdate, ) ( bigNetCollateral *big.Int, bigInitialMargin *big.Int, @@ -862,7 +914,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_helper.go b/protocol/x/subaccounts/keeper/subaccount_helper.go index 71721cb9a6d..79b14fbc2e6 100644 --- a/protocol/x/subaccounts/keeper/subaccount_helper.go +++ b/protocol/x/subaccounts/keeper/subaccount_helper.go @@ -12,7 +12,7 @@ import ( // been updated. This will include any asset postions that were closed due to an update. // TODO(DEC-1295): look into reducing code duplication here using Generics+Reflect. func getUpdatedAssetPositions( - update settledUpdate, + update SettledUpdate, ) []*types.AssetPosition { assetIdToPositionMap := make(map[uint32]*types.AssetPosition) for _, assetPosition := range update.SettledSubaccount.AssetPositions { @@ -53,7 +53,7 @@ func getUpdatedAssetPositions( // been updated. This will include any perpetual postions that were closed due to an update or that // received / paid out funding payments.. func getUpdatedPerpetualPositions( - update settledUpdate, + update SettledUpdate, fundingPayments map[uint32]dtypes.SerializableInt, ) []*types.PerpetualPosition { perpetualIdToPositionMap := make(map[uint32]*types.PerpetualPosition) @@ -102,7 +102,7 @@ func getUpdatedPerpetualPositions( // to reflect settledUpdate.PerpetualUpdates. // For newly created positions, use `perpIdToFundingIndex` map to populate the `FundingIndex` field. func UpdatePerpetualPositions( - settledUpdates []settledUpdate, + settledUpdates []SettledUpdate, perpIdToFundingIndex map[uint32]dtypes.SerializableInt, ) { // Apply the updates. @@ -166,7 +166,7 @@ func UpdatePerpetualPositions( // For each settledUpdate in settledUpdates, updates its SettledSubaccount.AssetPositions // to reflect settledUpdate.AssetUpdates. func UpdateAssetPositions( - settledUpdates []settledUpdate, + settledUpdates []SettledUpdate, ) { // Apply the updates. for i, u := range settledUpdates { diff --git a/protocol/x/subaccounts/keeper/subaccount_test.go b/protocol/x/subaccounts/keeper/subaccount_test.go index 49fcaab6855..b4ecd32cb1a 100644 --- a/protocol/x/subaccounts/keeper/subaccount_test.go +++ b/protocol/x/subaccounts/keeper/subaccount_test.go @@ -2326,7 +2326,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 { @@ -2651,7 +2651,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, }, @@ -2685,6 +2685,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, @@ -2698,7 +2708,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, }, @@ -2732,6 +2742,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, @@ -2745,7 +2765,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, }, @@ -2779,6 +2799,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, @@ -2788,9 +2818,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, }, @@ -2810,6 +2843,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, @@ -2820,9 +2863,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, }, @@ -2842,6 +2888,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, @@ -2852,9 +2908,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, }, @@ -2874,6 +2933,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, @@ -3146,20 +3215,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}, @@ -3184,6 +3619,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(1)), }, }, + updateType: types.Deposit, expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, }, @@ -3221,6 +3657,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { }, }, }, + updateType: types.Deposit, expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, }, @@ -3546,6 +3983,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { updates: []types.Update{ {}, }, + updateType: types.Deposit, expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, }, @@ -3566,6 +4004,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { updates: []types.Update{ {}, }, + updateType: types.Deposit, expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, }, @@ -3588,6 +4027,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(3)), // $3 }, }, + updateType: types.Deposit, expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, }, @@ -3808,6 +4248,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] @@ -3824,7 +4273,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/keeper/update.go b/protocol/x/subaccounts/keeper/update.go index 179139452a3..30e0ad58f7c 100644 --- a/protocol/x/subaccounts/keeper/update.go +++ b/protocol/x/subaccounts/keeper/update.go @@ -4,11 +4,11 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" ) -// settledUpdate is used internally in the subaccounts keeper to +// SettledUpdate is used internally in the subaccounts keeper to // to specify changes to one or more `Subaccounts` (for example the // result of a trade, transfer, etc). // The subaccount is always in its settled state. -type settledUpdate struct { +type SettledUpdate struct { // The `Subaccount` for which this update applies to, in its settled form. SettledSubaccount types.Subaccount // A list of changes to make to any `AssetPositions` in the `Subaccount`. diff --git a/protocol/x/subaccounts/types/errors.go b/protocol/x/subaccounts/types/errors.go index 91b4da30d20..1b5315b187d 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 4d30eb81747..8f0c9dd2172 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 684b618b28e..e591bea40bc 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 {