From 28eb87efe33967f08bf03fc3113b4abf058cd154 Mon Sep 17 00:00:00 2001 From: Walter White <101130700+NibiruHeisenberg@users.noreply.github.com> Date: Fri, 8 Jul 2022 08:57:24 -0700 Subject: [PATCH] refactor(perp): Organize Keeper methods (#684) * Reorganize keeper methods * Remove GetPosition and SetPosition * Remove ClearPosition * Remove unused Params state * Remove unnecessary Logger statements * Update CHANGELOG.md Co-authored-by: AgentSmithMatrix <98403347+AgentSmithMatrix@users.noreply.github.com> --- CHANGELOG.md | 5 +- x/perp/keeper/clearing_house.go | 223 +---------- x/perp/keeper/clearing_house_test.go | 535 +-------------------------- x/perp/keeper/grpc_query.go | 11 + x/perp/keeper/grpc_query_test.go | 2 +- x/perp/keeper/keeper.go | 6 - x/perp/keeper/liquidate.go | 4 +- x/perp/keeper/liquidate_test.go | 16 +- x/perp/keeper/liquidate_unit_test.go | 14 +- x/perp/keeper/margin.go | 166 ++++++++- x/perp/keeper/margin_test.go | 5 +- x/perp/keeper/margin_unit_test.go | 491 +++++++++++++++++++++++- x/perp/keeper/perp.go | 28 +- x/perp/keeper/perp_test.go | 41 +- x/perp/keeper/state.go | 60 +-- x/perp/keeper/state_test.go | 49 +++ 16 files changed, 775 insertions(+), 881 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72c9e2ce8..90e2048d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -### Improvements -* [#686](https://github.com/NibiruChain/nibiru/pull/686) Add changelog enforcer to github actions. - +- [#686](https://github.com/NibiruChain/nibiru/pull/686) Add changelog enforcer to github actions. +- [#686](https://github.com/NibiruChain/nibiru/pull/686) Reorganize PerpKeeper methods \ No newline at end of file diff --git a/x/perp/keeper/clearing_house.go b/x/perp/keeper/clearing_house.go index 74ea8d872..e06ecc322 100644 --- a/x/perp/keeper/clearing_house.go +++ b/x/perp/keeper/clearing_house.go @@ -29,11 +29,11 @@ func (k Keeper) OpenPosition( params := k.GetParams(ctx) // TODO: missing checks - position, err := k.GetPosition(ctx, pair, traderAddr) + position, err := k.PositionsState(ctx).Get(pair, traderAddr) var isNewPosition bool = errors.Is(err, types.ErrPositionNotFound) if isNewPosition { position = types.ZeroPosition(ctx, pair, traderAddr) - k.SetPosition(ctx, pair, traderAddr, position) + k.PositionsState(ctx).Set(pair, traderAddr, position) } else if err != nil && !isNewPosition { return err } @@ -86,7 +86,7 @@ func (k Keeper) afterPositionUpdate( ) (err error) { // update position in state if !positionResp.Position.Size_.IsZero() { - k.SetPosition(ctx, pair, traderAddr, positionResp.Position) + k.PositionsState(ctx).Set(pair, traderAddr, positionResp.Position) } if !isNewPosition && !positionResp.Position.Size_.IsZero() { @@ -252,119 +252,6 @@ func (k Keeper) increasePosition( return positionResp, nil } -// getLatestCumulativePremiumFraction returns the last cumulative premium fraction recorded for the -// specific pair. -func (k Keeper) getLatestCumulativePremiumFraction( - ctx sdk.Context, pair common.AssetPair, -) (sdk.Dec, error) { - pairMetadata, err := k.PairMetadataState(ctx).Get(pair) - if err != nil { - k.Logger(ctx).Error( - err.Error(), - "pair", - pair.String(), - ) - return sdk.Dec{}, err - } - // this should never fail - return pairMetadata.CumulativePremiumFractions[len(pairMetadata.CumulativePremiumFractions)-1], nil -} - -/* -Calculates position notional value and unrealized PnL. Lets the caller pick -either spot price, TWAP, or ORACLE to use for calculation. - -args: - - ctx: cosmos-sdk context - - position: the trader's position - - pnlCalcOption: SPOT or TWAP or ORACLE - -Returns: - - positionNotional: the position's notional value as sdk.Dec (signed) - - unrealizedPnl: the position's unrealized profits and losses (PnL) as sdk.Dec (signed) - For LONG positions, this is positionNotional - openNotional - For SHORT positions, this is openNotional - positionNotional -*/ -func (k Keeper) getPositionNotionalAndUnrealizedPnL( - ctx sdk.Context, - currentPosition types.Position, - pnlCalcOption types.PnLCalcOption, -) (positionNotional sdk.Dec, unrealizedPnL sdk.Dec, err error) { - positionSizeAbs := currentPosition.Size_.Abs() - if positionSizeAbs.IsZero() { - return sdk.ZeroDec(), sdk.ZeroDec(), nil - } - - var baseAssetDirection vpooltypes.Direction - if currentPosition.Size_.IsPositive() { - // LONG - baseAssetDirection = vpooltypes.Direction_ADD_TO_POOL - } else { - // SHORT - baseAssetDirection = vpooltypes.Direction_REMOVE_FROM_POOL - } - - switch pnlCalcOption { - case types.PnLCalcOption_TWAP: - positionNotional, err = k.VpoolKeeper.GetBaseAssetTWAP( - ctx, - currentPosition.Pair, - baseAssetDirection, - positionSizeAbs, - /*lookbackInterval=*/ k.GetParams(ctx).TwapLookbackWindow, - ) - if err != nil { - k.Logger(ctx).Error(err.Error(), "calc_option", pnlCalcOption.String()) - return sdk.ZeroDec(), sdk.ZeroDec(), err - } - case types.PnLCalcOption_SPOT_PRICE: - positionNotional, err = k.VpoolKeeper.GetBaseAssetPrice( - ctx, - currentPosition.Pair, - baseAssetDirection, - positionSizeAbs, - ) - if err != nil { - k.Logger(ctx).Error(err.Error(), "calc_option", pnlCalcOption.String()) - return sdk.ZeroDec(), sdk.ZeroDec(), err - } - case types.PnLCalcOption_ORACLE: - oraclePrice, err := k.VpoolKeeper.GetUnderlyingPrice( - ctx, currentPosition.Pair) - if err != nil { - k.Logger(ctx).Error(err.Error(), "calc_option", pnlCalcOption.String()) - return sdk.ZeroDec(), sdk.ZeroDec(), err - } - positionNotional = oraclePrice.Mul(positionSizeAbs) - default: - panic("unrecognized pnl calc option: " + pnlCalcOption.String()) - } - - if positionNotional.Equal(currentPosition.OpenNotional) { - // if position notional and open notional are the same, then early return - return positionNotional, sdk.ZeroDec(), nil - } - - if currentPosition.Size_.IsPositive() { - // LONG - unrealizedPnL = positionNotional.Sub(currentPosition.OpenNotional) - } else { - // SHORT - unrealizedPnL = currentPosition.OpenNotional.Sub(positionNotional) - } - - k.Logger(ctx).Debug("get_position_notional_and_unrealized_pnl", - "position", - currentPosition.String(), - "position_notional", - positionNotional.String(), - "unrealized_pnl", - unrealizedPnL.String(), - ) - - return positionNotional, unrealizedPnL, nil -} - // TODO test: openReversePosition | https://github.com/NibiruChain/nibiru/issues/299 func (k Keeper) openReversePosition( ctx sdk.Context, @@ -516,11 +403,6 @@ func (k Keeper) decreasePosition( BlockNumber: ctx.BlockHeight(), } - k.Logger(ctx).Debug("decrease_position", - "positionResp", - positionResp.String(), - ) - return positionResp, nil } @@ -628,11 +510,6 @@ func (k Keeper) closeAndOpenReversePosition( positionResp = closePositionResp } - k.Logger(ctx).Debug("close_and_open_reverse_position", - "positionResp", - positionResp.String(), - ) - return positionResp, nil } @@ -720,25 +597,19 @@ func (k Keeper) closePositionEntirely( BlockNumber: ctx.BlockHeight(), } - if err = k.ClearPosition( - ctx, + if err = k.PositionsState(ctx).Delete( currentPosition.Pair, trader, ); err != nil { return nil, err } - k.Logger(ctx).Debug("close_position_entirely", - "positionResp", - positionResp.String(), - ) - return positionResp, nil } // ClosePosition gets the current position, and calls OpenPosition to open a reverse position with amount equal to the current open notional. func (k Keeper) ClosePosition(ctx sdk.Context, pair common.AssetPair, addr sdk.AccAddress) (*types.PositionResp, error) { - position, err := k.GetPosition(ctx, pair, addr) + position, err := k.PositionsState(ctx).Get(pair, addr) if err != nil { return nil, err } @@ -818,72 +689,6 @@ func (k Keeper) transferFee( return feeToFeePool.Add(feeToEcosystemFund), nil } -/* -Calculates both position notional value and unrealized PnL based on -both spot price and TWAP, and lets the caller pick which one based on MAX or MIN. - -args: - - ctx: cosmos-sdk context - - position: the trader's position - - pnlPreferenceOption: MAX or MIN - -Returns: - - positionNotional: the position's notional value as sdk.Dec (signed) - - unrealizedPnl: the position's unrealized profits and losses (PnL) as sdk.Dec (signed) - For LONG positions, this is positionNotional - openNotional - For SHORT positions, this is openNotional - positionNotional -*/ -func (k Keeper) getPreferencePositionNotionalAndUnrealizedPnL( - ctx sdk.Context, - position types.Position, - pnLPreferenceOption types.PnLPreferenceOption, -) (positionNotional sdk.Dec, unrealizedPnl sdk.Dec, err error) { - spotPositionNotional, spotPricePnl, err := k.getPositionNotionalAndUnrealizedPnL( - ctx, - position, - types.PnLCalcOption_SPOT_PRICE, - ) - if err != nil { - k.Logger(ctx).Error( - err.Error(), - "calc_option", - types.PnLCalcOption_SPOT_PRICE.String(), - "preference_option", - pnLPreferenceOption.String(), - ) - return sdk.Dec{}, sdk.Dec{}, err - } - - twapPositionNotional, twapPricePnL, err := k.getPositionNotionalAndUnrealizedPnL( - ctx, - position, - types.PnLCalcOption_TWAP, - ) - if err != nil { - k.Logger(ctx).Error( - err.Error(), - "calc_option", - types.PnLCalcOption_TWAP.String(), - "preference_option", - pnLPreferenceOption.String(), - ) - return sdk.Dec{}, sdk.Dec{}, err - } - - switch pnLPreferenceOption { - case types.PnLPreferenceOption_MAX: - positionNotional = sdk.MaxDec(spotPositionNotional, twapPositionNotional) - unrealizedPnl = sdk.MaxDec(spotPricePnl, twapPricePnL) - case types.PnLPreferenceOption_MIN: - positionNotional = sdk.MinDec(spotPositionNotional, twapPositionNotional) - unrealizedPnl = sdk.MinDec(spotPricePnl, twapPricePnL) - default: - panic("invalid pnl preference option " + pnLPreferenceOption.String()) - } - - return positionNotional, unrealizedPnl, nil -} - /* Trades quoteAssets in exchange for baseAssets. The quote asset is a stablecoin like NUSD. @@ -920,27 +725,9 @@ func (k Keeper) swapQuoteForBase( baseAmount, err = k.VpoolKeeper.SwapQuoteForBase( ctx, pair, quoteAssetDirection, quoteAssetAmount, baseAssetLimit) if err != nil { - k.Logger(ctx).Error( - err.Error(), - "pair", - pair.String(), - "side", - side.String(), - "quoteAssetAmount", - quoteAssetAmount.String(), - "baseAssetLimit", - baseAssetLimit.String(), - ) return sdk.Dec{}, err } - k.Logger(ctx).Debug("swap_quote_for_base", - "side", - side.String(), - "baseAmt", - baseAmount.Abs(), - ) - if side == types.Side_BUY { return baseAmount, nil } else { diff --git a/x/perp/keeper/clearing_house_test.go b/x/perp/keeper/clearing_house_test.go index 498bd983d..1a72f4aef 100644 --- a/x/perp/keeper/clearing_house_test.go +++ b/x/perp/keeper/clearing_house_test.go @@ -3,7 +3,6 @@ package keeper import ( "fmt" "testing" - "time" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -27,55 +26,6 @@ import ( vpooltypes "github.com/NibiruChain/nibiru/x/vpool/types" ) -func TestGetLatestCumulativePremiumFraction(t *testing.T) { - testCases := []struct { - name string - test func() - }{ - { - name: "happy path", - test: func() { - keeper, _, ctx := getKeeper(t) - - metadata := &types.PairMetadata{ - Pair: common.PairGovStable, - CumulativePremiumFractions: []sdk.Dec{ - sdk.NewDec(1), - sdk.NewDec(2), // returns the latest from the list - }, - } - keeper.PairMetadataState(ctx).Set(metadata) - - latestCumulativePremiumFraction, err := keeper. - getLatestCumulativePremiumFraction(ctx, common.PairGovStable) - - require.NoError(t, err) - assert.Equal(t, sdk.NewDec(2), latestCumulativePremiumFraction) - }, - }, - { - name: "uninitialized vpool has no metadata | fail", - test: func() { - perpKeeper, _, ctx := getKeeper(t) - vpool := common.AssetPair{ - Token0: "xxx", - Token1: "yyy", - } - lcpf, err := perpKeeper.getLatestCumulativePremiumFraction( - ctx, vpool) - require.Error(t, err) - assert.EqualValues(t, sdk.Dec{}, lcpf) - }, - }, - } - for _, testCase := range testCases { - tc := testCase - t.Run(tc.name, func(t *testing.T) { - tc.test() - }) - } -} - type mockedDependencies struct { mockAccountKeeper *mock.MockAccountKeeper mockBankKeeper *mock.MockBankKeeper @@ -151,310 +101,6 @@ func initParamsKeeper( return paramsKeeper } -func TestGetPositionNotionalAndUnrealizedPnl(t *testing.T) { - tests := []struct { - name string - initialPosition types.Position - setMocks func(ctx sdk.Context, mocks mockedDependencies) - pnlCalcOption types.PnLCalcOption - expectedPositionalNotional sdk.Dec - expectedUnrealizedPnL sdk.Dec - }{ - { - name: "long position; positive pnl; spot price calc", - initialPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetPrice( - ctx, - common.PairBTCStable, - vpooltypes.Direction_ADD_TO_POOL, - sdk.NewDec(10), - ). - Return(sdk.NewDec(20), nil) - }, - pnlCalcOption: types.PnLCalcOption_SPOT_PRICE, - expectedPositionalNotional: sdk.NewDec(20), - expectedUnrealizedPnL: sdk.NewDec(10), - }, - { - name: "long position; negative pnl; spot price calc", - initialPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetPrice( - ctx, - common.PairBTCStable, - vpooltypes.Direction_ADD_TO_POOL, - sdk.NewDec(10), - ). - Return(sdk.NewDec(5), nil) - }, - pnlCalcOption: types.PnLCalcOption_SPOT_PRICE, - expectedPositionalNotional: sdk.NewDec(5), - expectedUnrealizedPnL: sdk.NewDec(-5), - }, - { - name: "long position; positive pnl; twap calc", - initialPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetTWAP( - ctx, - common.PairBTCStable, - vpooltypes.Direction_ADD_TO_POOL, - sdk.NewDec(10), - 15*time.Minute, - ). - Return(sdk.NewDec(20), nil) - }, - pnlCalcOption: types.PnLCalcOption_TWAP, - expectedPositionalNotional: sdk.NewDec(20), - expectedUnrealizedPnL: sdk.NewDec(10), - }, - { - name: "long position; negative pnl; twap calc", - initialPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetTWAP( - ctx, - common.PairBTCStable, - vpooltypes.Direction_ADD_TO_POOL, - sdk.NewDec(10), - 15*time.Minute, - ). - Return(sdk.NewDec(5), nil) - }, - pnlCalcOption: types.PnLCalcOption_TWAP, - expectedPositionalNotional: sdk.NewDec(5), - expectedUnrealizedPnL: sdk.NewDec(-5), - }, - { - name: "long position; positive pnl; oracle calc", - initialPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - mocks.mockVpoolKeeper.EXPECT(). - GetUnderlyingPrice( - ctx, - common.PairBTCStable, - ). - Return(sdk.NewDec(2), nil) - }, - pnlCalcOption: types.PnLCalcOption_ORACLE, - expectedPositionalNotional: sdk.NewDec(20), - expectedUnrealizedPnL: sdk.NewDec(10), - }, - { - name: "long position; negative pnl; oracle calc", - initialPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - mocks.mockVpoolKeeper.EXPECT(). - GetUnderlyingPrice( - ctx, - common.PairBTCStable, - ). - Return(sdk.MustNewDecFromStr("0.5"), nil) - }, - pnlCalcOption: types.PnLCalcOption_ORACLE, - expectedPositionalNotional: sdk.NewDec(5), - expectedUnrealizedPnL: sdk.NewDec(-5), - }, - { - name: "short position; positive pnl; spot price calc", - initialPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(-10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetPrice( - ctx, - common.PairBTCStable, - vpooltypes.Direction_REMOVE_FROM_POOL, - sdk.NewDec(10), - ). - Return(sdk.NewDec(5), nil) - }, - pnlCalcOption: types.PnLCalcOption_SPOT_PRICE, - expectedPositionalNotional: sdk.NewDec(5), - expectedUnrealizedPnL: sdk.NewDec(5), - }, - { - name: "short position; negative pnl; spot price calc", - initialPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(-10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetPrice( - ctx, - common.PairBTCStable, - vpooltypes.Direction_REMOVE_FROM_POOL, - sdk.NewDec(10), - ). - Return(sdk.NewDec(20), nil) - }, - pnlCalcOption: types.PnLCalcOption_SPOT_PRICE, - expectedPositionalNotional: sdk.NewDec(20), - expectedUnrealizedPnL: sdk.NewDec(-10), - }, - { - name: "short position; positive pnl; twap calc", - initialPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(-10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetTWAP( - ctx, - common.PairBTCStable, - vpooltypes.Direction_REMOVE_FROM_POOL, - sdk.NewDec(10), - 15*time.Minute, - ). - Return(sdk.NewDec(5), nil) - }, - pnlCalcOption: types.PnLCalcOption_TWAP, - expectedPositionalNotional: sdk.NewDec(5), - expectedUnrealizedPnL: sdk.NewDec(5), - }, - { - name: "short position; negative pnl; twap calc", - initialPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(-10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetTWAP( - ctx, - common.PairBTCStable, - vpooltypes.Direction_REMOVE_FROM_POOL, - sdk.NewDec(10), - 15*time.Minute, - ). - Return(sdk.NewDec(20), nil) - }, - pnlCalcOption: types.PnLCalcOption_TWAP, - expectedPositionalNotional: sdk.NewDec(20), - expectedUnrealizedPnL: sdk.NewDec(-10), - }, - { - name: "short position; positive pnl; oracle calc", - initialPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(-10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - mocks.mockVpoolKeeper.EXPECT(). - GetUnderlyingPrice( - ctx, - common.PairBTCStable, - ). - Return(sdk.MustNewDecFromStr("0.5"), nil) - }, - pnlCalcOption: types.PnLCalcOption_ORACLE, - expectedPositionalNotional: sdk.NewDec(5), - expectedUnrealizedPnL: sdk.NewDec(5), - }, - { - name: "long position; negative pnl; oracle calc", - initialPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(-10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - mocks.mockVpoolKeeper.EXPECT(). - GetUnderlyingPrice( - ctx, - common.PairBTCStable, - ). - Return(sdk.NewDec(2), nil) - }, - pnlCalcOption: types.PnLCalcOption_ORACLE, - expectedPositionalNotional: sdk.NewDec(20), - expectedUnrealizedPnL: sdk.NewDec(-10), - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - perpKeeper, mocks, ctx := getKeeper(t) - - tc.setMocks(ctx, mocks) - - positionalNotional, unrealizedPnl, err := perpKeeper. - getPositionNotionalAndUnrealizedPnL( - ctx, - tc.initialPosition, - tc.pnlCalcOption, - ) - require.NoError(t, err) - - assert.EqualValues(t, tc.expectedPositionalNotional, positionalNotional) - assert.EqualValues(t, tc.expectedUnrealizedPnL, unrealizedPnl) - }) - } -} - func TestSwapQuoteAssetForBase(t *testing.T) { tests := []struct { name string @@ -516,177 +162,6 @@ func TestSwapQuoteAssetForBase(t *testing.T) { } } -func TestGetPreferencePositionNotionalAndUnrealizedPnl(t *testing.T) { - // all tests are assumed long positions with positive pnl for ease of calculation - // short positions and negative pnl are implicitly correct because of - // TestGetPositionNotionalAndUnrealizedPnl - testcases := []struct { - name string - initPosition types.Position - setMocks func(ctx sdk.Context, mocks mockedDependencies) - pnlPreferenceOption types.PnLPreferenceOption - expectedPositionalNotional sdk.Dec - expectedUnrealizedPnl sdk.Dec - }{ - { - name: "max pnl, pick spot price", - initPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - t.Log("Mock vpool spot price") - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetPrice( - ctx, - common.PairBTCStable, - vpooltypes.Direction_ADD_TO_POOL, - sdk.NewDec(10), - ). - Return(sdk.NewDec(20), nil) - t.Log("Mock vpool twap") - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetTWAP( - ctx, - common.PairBTCStable, - vpooltypes.Direction_ADD_TO_POOL, - sdk.NewDec(10), - 15*time.Minute, - ). - Return(sdk.NewDec(15), nil) - }, - pnlPreferenceOption: types.PnLPreferenceOption_MAX, - expectedPositionalNotional: sdk.NewDec(20), - expectedUnrealizedPnl: sdk.NewDec(10), - }, - { - name: "max pnl, pick twap", - initPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - t.Log("Mock vpool spot price") - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetPrice( - ctx, - common.PairBTCStable, - vpooltypes.Direction_ADD_TO_POOL, - sdk.NewDec(10), - ). - Return(sdk.NewDec(20), nil) - t.Log("Mock vpool twap") - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetTWAP( - ctx, - common.PairBTCStable, - vpooltypes.Direction_ADD_TO_POOL, - sdk.NewDec(10), - 15*time.Minute, - ). - Return(sdk.NewDec(30), nil) - }, - pnlPreferenceOption: types.PnLPreferenceOption_MAX, - expectedPositionalNotional: sdk.NewDec(30), - expectedUnrealizedPnl: sdk.NewDec(20), - }, - { - name: "min pnl, pick spot price", - initPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - t.Log("Mock vpool spot price") - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetPrice( - ctx, - common.PairBTCStable, - vpooltypes.Direction_ADD_TO_POOL, - sdk.NewDec(10), - ). - Return(sdk.NewDec(20), nil) - t.Log("Mock vpool twap") - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetTWAP( - ctx, - common.PairBTCStable, - vpooltypes.Direction_ADD_TO_POOL, - sdk.NewDec(10), - 15*time.Minute, - ). - Return(sdk.NewDec(30), nil) - }, - pnlPreferenceOption: types.PnLPreferenceOption_MIN, - expectedPositionalNotional: sdk.NewDec(20), - expectedUnrealizedPnl: sdk.NewDec(10), - }, - { - name: "min pnl, pick twap", - initPosition: types.Position{ - TraderAddress: sample.AccAddress().String(), - Pair: common.PairBTCStable, - Size_: sdk.NewDec(10), - OpenNotional: sdk.NewDec(10), - Margin: sdk.NewDec(1), - }, - setMocks: func(ctx sdk.Context, mocks mockedDependencies) { - t.Log("Mock vpool spot price") - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetPrice( - ctx, - common.PairBTCStable, - vpooltypes.Direction_ADD_TO_POOL, - sdk.NewDec(10), - ). - Return(sdk.NewDec(20), nil) - t.Log("Mock vpool twap") - mocks.mockVpoolKeeper.EXPECT(). - GetBaseAssetTWAP( - ctx, - common.PairBTCStable, - vpooltypes.Direction_ADD_TO_POOL, - sdk.NewDec(10), - 15*time.Minute, - ). - Return(sdk.NewDec(15), nil) - }, - pnlPreferenceOption: types.PnLPreferenceOption_MIN, - expectedPositionalNotional: sdk.NewDec(15), - expectedUnrealizedPnl: sdk.NewDec(5), - }, - } - - for _, tc := range testcases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - perpKeeper, mocks, ctx := getKeeper(t) - - tc.setMocks(ctx, mocks) - - positionalNotional, unrealizedPnl, err := perpKeeper. - getPreferencePositionNotionalAndUnrealizedPnL( - ctx, - tc.initPosition, - tc.pnlPreferenceOption, - ) - - require.NoError(t, err) - assert.EqualValues(t, tc.expectedPositionalNotional, positionalNotional) - assert.EqualValues(t, tc.expectedUnrealizedPnl, unrealizedPnl) - }) - } -} - func TestIncreasePosition(t *testing.T) { tests := []struct { name string @@ -1374,8 +849,7 @@ func TestClosePositionEntirely(t *testing.T) { trader, err := sdk.AccAddressFromBech32(tc.initialPosition.TraderAddress) require.NoError(t, err) - perpKeeper.SetPosition( - ctx, + perpKeeper.PositionsState(ctx).Set( tc.initialPosition.Pair, trader, &tc.initialPosition, @@ -2074,8 +1548,7 @@ func TestCloseAndOpenReversePosition(t *testing.T) { LastUpdateCumulativePremiumFraction: sdk.ZeroDec(), BlockNumber: 0, } - perpKeeper.SetPosition( - ctx, + perpKeeper.PositionsState(ctx).Set( common.PairBTCStable, traderAddr, ¤tPosition, @@ -2358,7 +1831,7 @@ func TestClosePosition(t *testing.T) { require.NoError(t, err) t.Log("set position") - perpKeeper.SetPosition(ctx, tc.initialPosition.Pair, traderAddr, &tc.initialPosition) + perpKeeper.PositionsState(ctx).Set(tc.initialPosition.Pair, traderAddr, &tc.initialPosition) t.Log("set params") params := types.DefaultParams() @@ -2526,7 +1999,7 @@ func TestClosePositionWithBadDebt(t *testing.T) { require.NoError(t, err) t.Log("set position") - perpKeeper.SetPosition(ctx, tc.initialPosition.Pair, traderAddr, &tc.initialPosition) + perpKeeper.PositionsState(ctx).Set(tc.initialPosition.Pair, traderAddr, &tc.initialPosition) t.Log("set params") perpKeeper.SetParams(ctx, types.DefaultParams()) diff --git a/x/perp/keeper/grpc_query.go b/x/perp/keeper/grpc_query.go index c4375472b..122e75f07 100644 --- a/x/perp/keeper/grpc_query.go +++ b/x/perp/keeper/grpc_query.go @@ -61,3 +61,14 @@ func (q queryServer) TraderPosition( MarginRatio: marginRatio, }, nil } + +func (q queryServer) Params( + goCtx context.Context, req *types.QueryParamsRequest, +) (*types.QueryParamsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(goCtx) + + return &types.QueryParamsResponse{Params: q.Keeper.GetParams(ctx)}, nil +} diff --git a/x/perp/keeper/grpc_query_test.go b/x/perp/keeper/grpc_query_test.go index 687da7b51..90d81e55b 100644 --- a/x/perp/keeper/grpc_query_test.go +++ b/x/perp/keeper/grpc_query_test.go @@ -107,7 +107,7 @@ func TestQueryPosition(t *testing.T) { }) t.Log("initialize position") - perpKeeper.SetPosition(ctx, common.PairBTCStable, traderAddr, tc.initialPosition) + perpKeeper.PositionsState(ctx).Set(common.PairBTCStable, traderAddr, tc.initialPosition) t.Log("query position") resp, err := queryServer.TraderPosition( diff --git a/x/perp/keeper/keeper.go b/x/perp/keeper/keeper.go index 92acb915e..ad49b9055 100644 --- a/x/perp/keeper/keeper.go +++ b/x/perp/keeper/keeper.go @@ -62,12 +62,6 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } -// GetModuleAccountBalance gets the airdrop coin balance of module account. -func (k Keeper) GetModuleAccountBalance(ctx sdk.Context, denom string) sdk.Coin { - moduleAccAddr := k.AccountKeeper.GetModuleAddress(types.ModuleName) - return k.BankKeeper.GetBalance(ctx, moduleAccAddr, denom) -} - // GetParams get all parameters as types.Params func (k *Keeper) GetParams(ctx sdk.Context) (params types.Params) { k.ParamSubspace.GetParamSet(ctx, ¶ms) diff --git a/x/perp/keeper/liquidate.go b/x/perp/keeper/liquidate.go index 0f3ad8040..63d699cfb 100644 --- a/x/perp/keeper/liquidate.go +++ b/x/perp/keeper/liquidate.go @@ -32,7 +32,7 @@ func (k Keeper) Liquidate( return sdk.Coin{}, sdk.Coin{}, err } - position, err := k.GetPosition(ctx, pair, traderAddr) + position, err := k.PositionsState(ctx).Get(pair, traderAddr) if err != nil { return sdk.Coin{}, sdk.Coin{}, err } @@ -292,7 +292,7 @@ func (k Keeper) ExecutePartialLiquidation( Mul(params.LiquidationFeeRatio) positionResp.Position.Margin = positionResp.Position.Margin. Sub(liquidationFeeAmount) - k.SetPosition(ctx, currentPosition.Pair, traderAddr, + k.PositionsState(ctx).Set(currentPosition.Pair, traderAddr, positionResp.Position) // Compute splits for the liquidation fee diff --git a/x/perp/keeper/liquidate_test.go b/x/perp/keeper/liquidate_test.go index fe4e38d44..525b3c09c 100644 --- a/x/perp/keeper/liquidate_test.go +++ b/x/perp/keeper/liquidate_test.go @@ -99,7 +99,7 @@ func TestExecuteFullLiquidation_EmptyPosition(t *testing.T) { require.NoError(t, err) t.Log("Get the position") - position, err := nibiruApp.PerpKeeper.GetPosition(ctx, pair, trader) + position, err := nibiruApp.PerpKeeper.PositionsState(ctx).Get(pair, trader) require.NoError(t, err) t.Log("Artificially populate Vault and PerpEF to prevent BankKeeper errors") @@ -117,7 +117,7 @@ func TestExecuteFullLiquidation_EmptyPosition(t *testing.T) { require.Error(t, err) // No change in the position - newPosition, _ := nibiruApp.PerpKeeper.GetPosition(ctx, pair, trader) + newPosition, _ := nibiruApp.PerpKeeper.PositionsState(ctx).Get(pair, trader) assert.Equal(t, position.Size_, newPosition.Size_) assert.Equal(t, position.Margin, newPosition.Margin) assert.Equal(t, position.OpenNotional, newPosition.OpenNotional) @@ -273,7 +273,7 @@ func TestExecuteFullLiquidation(t *testing.T) { require.NoError(t, err) t.Log("Get the position") - position, err := nibiruApp.PerpKeeper.GetPosition(ctx, tokenPair, traderAddr) + position, err := nibiruApp.PerpKeeper.PositionsState(ctx).Get(tokenPair, traderAddr) require.NoError(t, err) t.Log("Artificially populate Vault and PerpEF to prevent BankKeeper errors") @@ -290,7 +290,7 @@ func TestExecuteFullLiquidation(t *testing.T) { require.NoError(t, err) t.Log("Check correctness of new position") - newPosition, err := nibiruApp.PerpKeeper.GetPosition(ctx, tokenPair, traderAddr) + newPosition, err := nibiruApp.PerpKeeper.PositionsState(ctx).Get(tokenPair, traderAddr) require.ErrorIs(t, err, types.ErrPositionNotFound) require.Nil(t, newPosition) @@ -399,7 +399,7 @@ func TestExecutePartialLiquidation_EmptyPosition(t *testing.T) { ctx, pair, tc.side, trader, tc.quote, tc.leverage, tc.baseLimit)) t.Log("Get the position") - position, err := nibiruApp.PerpKeeper.GetPosition(ctx, pair, trader) + position, err := nibiruApp.PerpKeeper.PositionsState(ctx).Get(pair, trader) require.NoError(t, err) t.Log("Artificially populate Vault and PerpEF to prevent BankKeeper errors") @@ -417,7 +417,7 @@ func TestExecutePartialLiquidation_EmptyPosition(t *testing.T) { require.Error(t, err) // No change in the position - newPosition, _ := nibiruApp.PerpKeeper.GetPosition(ctx, pair, trader) + newPosition, _ := nibiruApp.PerpKeeper.PositionsState(ctx).Get(pair, trader) require.Equal(t, position.Size_, newPosition.Size_) require.Equal(t, position.Margin, newPosition.Margin) require.Equal(t, position.OpenNotional, newPosition.OpenNotional) @@ -551,7 +551,7 @@ func TestExecutePartialLiquidation(t *testing.T) { require.NoError(t, err) t.Log("Get the position") - position, err := nibiruApp.PerpKeeper.GetPosition(ctx, tokenPair, traderAddr) + position, err := nibiruApp.PerpKeeper.PositionsState(ctx).Get(tokenPair, traderAddr) require.NoError(t, err) t.Log("Artificially populate Vault and PerpEF to prevent BankKeeper errors") @@ -568,7 +568,7 @@ func TestExecutePartialLiquidation(t *testing.T) { require.NoError(t, err) t.Log("Check correctness of new position") - newPosition, _ := nibiruApp.PerpKeeper.GetPosition(ctx, tokenPair, traderAddr) + newPosition, _ := nibiruApp.PerpKeeper.PositionsState(ctx).Get(tokenPair, traderAddr) assert.Equal(t, tc.expectedPositionSize, newPosition.Size_) assert.Equal(t, tc.expectedMarginRemaining, newPosition.Margin) diff --git a/x/perp/keeper/liquidate_unit_test.go b/x/perp/keeper/liquidate_unit_test.go index da369f2c0..66f69b379 100644 --- a/x/perp/keeper/liquidate_unit_test.go +++ b/x/perp/keeper/liquidate_unit_test.go @@ -93,7 +93,7 @@ func TestLiquidateIntoPartialLiquidation(t *testing.T) { Margin: tc.initialPositionMargin, OpenNotional: tc.initialPositionOpenNotional, } - perpKeeper.SetPosition(ctx, common.PairBTCStable, traderAddr, &position) + perpKeeper.PositionsState(ctx).Set(common.PairBTCStable, traderAddr, &position) t.Log("set params") params := types.DefaultParams() @@ -177,7 +177,7 @@ func TestLiquidateIntoPartialLiquidation(t *testing.T) { assert.EqualValues(t, tc.expectedPerpEFFee, feeToFund) t.Log("assert new position and event") - newPosition, err := perpKeeper.GetPosition(ctx, common.PairBTCStable, traderAddr) + newPosition, err := perpKeeper.PositionsState(ctx).Get(common.PairBTCStable, traderAddr) require.NoError(t, err) assert.EqualValues(t, traderAddr.String(), newPosition.TraderAddress) assert.EqualValues(t, common.PairBTCStable, newPosition.Pair) @@ -263,7 +263,7 @@ func TestLiquidateIntoFullLiquidation(t *testing.T) { Margin: tc.initialPositionMargin, OpenNotional: tc.initialPositionOpenNotional, } - perpKeeper.SetPosition(ctx, common.PairBTCStable, traderAddr, &position) + perpKeeper.PositionsState(ctx).Set(common.PairBTCStable, traderAddr, &position) t.Log("set params") params := types.DefaultParams() @@ -341,7 +341,7 @@ func TestLiquidateIntoFullLiquidation(t *testing.T) { assert.EqualValues(t, tc.expectedPerpEFFee.String(), feeToFund.String()) t.Log("assert new position and event") - newPosition, err := perpKeeper.GetPosition(ctx, common.PairBTCStable, traderAddr) + newPosition, err := perpKeeper.PositionsState(ctx).Get(common.PairBTCStable, traderAddr) require.ErrorIs(t, err, types.ErrPositionNotFound) assert.Nil(t, newPosition) @@ -430,7 +430,7 @@ func TestLiquidateIntoFullLiquidationWithBadDebt(t *testing.T) { Margin: tc.initialPositionMargin, OpenNotional: tc.initialPositionOpenNotional, } - perpKeeper.SetPosition(ctx, common.PairBTCStable, traderAddr, &position) + perpKeeper.PositionsState(ctx).Set(common.PairBTCStable, traderAddr, &position) t.Log("set params") params := types.DefaultParams() @@ -511,7 +511,7 @@ func TestLiquidateIntoFullLiquidationWithBadDebt(t *testing.T) { assert.EqualValues(t, tc.expectedPerpEFFee.String(), feeToFund.String()) t.Log("assert new position and event") - newPosition, err := perpKeeper.GetPosition(ctx, common.PairBTCStable, traderAddr) + newPosition, err := perpKeeper.PositionsState(ctx).Get(common.PairBTCStable, traderAddr) require.ErrorIs(t, err, types.ErrPositionNotFound) assert.Nil(t, newPosition) @@ -950,7 +950,7 @@ func TestExecuteFullLiquidation(t *testing.T) { LastUpdateCumulativePremiumFraction: sdk.ZeroDec(), BlockNumber: ctx.BlockHeight(), } - perpKeeper.SetPosition(ctx, common.PairBTCStable, traderAddr, &position) + perpKeeper.PositionsState(ctx).Set(common.PairBTCStable, traderAddr, &position) t.Log("execute full liquidation") liquidationResp, err := perpKeeper.ExecuteFullLiquidation( diff --git a/x/perp/keeper/margin.go b/x/perp/keeper/margin.go index 026a743f7..4456cc6c2 100644 --- a/x/perp/keeper/margin.go +++ b/x/perp/keeper/margin.go @@ -8,6 +8,7 @@ import ( "github.com/NibiruChain/nibiru/x/common" "github.com/NibiruChain/nibiru/x/perp/types" + vpooltypes "github.com/NibiruChain/nibiru/x/vpool/types" ) /* AddMargin deleverages an existing position by adding margin (collateral) @@ -48,7 +49,7 @@ func (k Keeper) AddMargin( } // ------------- AddMargin ------------- - position, err := k.GetPosition(ctx, pair, msgSender) + position, err := k.PositionsState(ctx).Get(pair, msgSender) if err != nil { return nil, err } @@ -74,7 +75,7 @@ func (k Keeper) AddMargin( position.Margin = remainingMargin.Margin position.LastUpdateCumulativePremiumFraction = remainingMargin.LatestCumulativePremiumFraction position.BlockNumber = ctx.BlockHeight() - k.SetPosition(ctx, pair, msgSender, position) + k.PositionsState(ctx).Set(pair, msgSender, position) err = ctx.EventManager().EmitTypedEvent( &types.MarginChangedEvent{ @@ -264,3 +265,164 @@ func requireMoreMarginRatio(marginRatio, baseMarginRatio sdk.Dec, largerThanOrEq } return nil } + +/* +Calculates position notional value and unrealized PnL. Lets the caller pick +either spot price, TWAP, or ORACLE to use for calculation. + +args: + - ctx: cosmos-sdk context + - position: the trader's position + - pnlCalcOption: SPOT or TWAP or ORACLE + +Returns: + - positionNotional: the position's notional value as sdk.Dec (signed) + - unrealizedPnl: the position's unrealized profits and losses (PnL) as sdk.Dec (signed) + For LONG positions, this is positionNotional - openNotional + For SHORT positions, this is openNotional - positionNotional +*/ +func (k Keeper) getPositionNotionalAndUnrealizedPnL( + ctx sdk.Context, + currentPosition types.Position, + pnlCalcOption types.PnLCalcOption, +) (positionNotional sdk.Dec, unrealizedPnL sdk.Dec, err error) { + positionSizeAbs := currentPosition.Size_.Abs() + if positionSizeAbs.IsZero() { + return sdk.ZeroDec(), sdk.ZeroDec(), nil + } + + var baseAssetDirection vpooltypes.Direction + if currentPosition.Size_.IsPositive() { + // LONG + baseAssetDirection = vpooltypes.Direction_ADD_TO_POOL + } else { + // SHORT + baseAssetDirection = vpooltypes.Direction_REMOVE_FROM_POOL + } + + switch pnlCalcOption { + case types.PnLCalcOption_TWAP: + positionNotional, err = k.VpoolKeeper.GetBaseAssetTWAP( + ctx, + currentPosition.Pair, + baseAssetDirection, + positionSizeAbs, + /*lookbackInterval=*/ k.GetParams(ctx).TwapLookbackWindow, + ) + if err != nil { + k.Logger(ctx).Error(err.Error(), "calc_option", pnlCalcOption.String()) + return sdk.ZeroDec(), sdk.ZeroDec(), err + } + case types.PnLCalcOption_SPOT_PRICE: + positionNotional, err = k.VpoolKeeper.GetBaseAssetPrice( + ctx, + currentPosition.Pair, + baseAssetDirection, + positionSizeAbs, + ) + if err != nil { + k.Logger(ctx).Error(err.Error(), "calc_option", pnlCalcOption.String()) + return sdk.ZeroDec(), sdk.ZeroDec(), err + } + case types.PnLCalcOption_ORACLE: + oraclePrice, err := k.VpoolKeeper.GetUnderlyingPrice( + ctx, currentPosition.Pair) + if err != nil { + k.Logger(ctx).Error(err.Error(), "calc_option", pnlCalcOption.String()) + return sdk.ZeroDec(), sdk.ZeroDec(), err + } + positionNotional = oraclePrice.Mul(positionSizeAbs) + default: + panic("unrecognized pnl calc option: " + pnlCalcOption.String()) + } + + if positionNotional.Equal(currentPosition.OpenNotional) { + // if position notional and open notional are the same, then early return + return positionNotional, sdk.ZeroDec(), nil + } + + if currentPosition.Size_.IsPositive() { + // LONG + unrealizedPnL = positionNotional.Sub(currentPosition.OpenNotional) + } else { + // SHORT + unrealizedPnL = currentPosition.OpenNotional.Sub(positionNotional) + } + + k.Logger(ctx).Debug("get_position_notional_and_unrealized_pnl", + "position", + currentPosition.String(), + "position_notional", + positionNotional.String(), + "unrealized_pnl", + unrealizedPnL.String(), + ) + + return positionNotional, unrealizedPnL, nil +} + +/* +Calculates both position notional value and unrealized PnL based on +both spot price and TWAP, and lets the caller pick which one based on MAX or MIN. + +args: + - ctx: cosmos-sdk context + - position: the trader's position + - pnlPreferenceOption: MAX or MIN + +Returns: + - positionNotional: the position's notional value as sdk.Dec (signed) + - unrealizedPnl: the position's unrealized profits and losses (PnL) as sdk.Dec (signed) + For LONG positions, this is positionNotional - openNotional + For SHORT positions, this is openNotional - positionNotional +*/ +func (k Keeper) getPreferencePositionNotionalAndUnrealizedPnL( + ctx sdk.Context, + position types.Position, + pnLPreferenceOption types.PnLPreferenceOption, +) (positionNotional sdk.Dec, unrealizedPnl sdk.Dec, err error) { + spotPositionNotional, spotPricePnl, err := k.getPositionNotionalAndUnrealizedPnL( + ctx, + position, + types.PnLCalcOption_SPOT_PRICE, + ) + if err != nil { + k.Logger(ctx).Error( + err.Error(), + "calc_option", + types.PnLCalcOption_SPOT_PRICE.String(), + "preference_option", + pnLPreferenceOption.String(), + ) + return sdk.Dec{}, sdk.Dec{}, err + } + + twapPositionNotional, twapPricePnL, err := k.getPositionNotionalAndUnrealizedPnL( + ctx, + position, + types.PnLCalcOption_TWAP, + ) + if err != nil { + k.Logger(ctx).Error( + err.Error(), + "calc_option", + types.PnLCalcOption_TWAP.String(), + "preference_option", + pnLPreferenceOption.String(), + ) + return sdk.Dec{}, sdk.Dec{}, err + } + + switch pnLPreferenceOption { + case types.PnLPreferenceOption_MAX: + positionNotional = sdk.MaxDec(spotPositionNotional, twapPositionNotional) + unrealizedPnl = sdk.MaxDec(spotPricePnl, twapPricePnL) + case types.PnLPreferenceOption_MIN: + positionNotional = sdk.MinDec(spotPositionNotional, twapPositionNotional) + unrealizedPnl = sdk.MinDec(spotPricePnl, twapPricePnL) + default: + panic("invalid pnl preference option " + pnLPreferenceOption.String()) + } + + return positionNotional, unrealizedPnl, nil +} diff --git a/x/perp/keeper/margin_test.go b/x/perp/keeper/margin_test.go index e47309616..c1f5fe19a 100644 --- a/x/perp/keeper/margin_test.go +++ b/x/perp/keeper/margin_test.go @@ -309,8 +309,7 @@ func TestAddMarginSuccess(t *testing.T) { ) t.Log("establish initial position") - nibiruApp.PerpKeeper.SetPosition( - ctx, + nibiruApp.PerpKeeper.PositionsState(ctx).Set( common.PairBTCStable, traderAddr, &tc.initialPosition, @@ -493,7 +492,7 @@ func TestRemoveMargin(t *testing.T) { require.NoError(t, err) t.Log("Position should be accessible following 'OpenPosition'") - _, err = nibiruApp.PerpKeeper.GetPosition(ctx, pair, traderAddr) + _, err = nibiruApp.PerpKeeper.PositionsState(ctx).Get(pair, traderAddr) require.NoError(t, err) t.Log("Verify correct events emitted for 'OpenPosition'") diff --git a/x/perp/keeper/margin_unit_test.go b/x/perp/keeper/margin_unit_test.go index 0a5c1b7cd..931732074 100644 --- a/x/perp/keeper/margin_unit_test.go +++ b/x/perp/keeper/margin_unit_test.go @@ -236,7 +236,7 @@ func TestRemoveMargin(t *testing.T) { }) t.Log("Set an underwater position, positive bad debt due to excessive margin request") - perpKeeper.SetPosition(ctx, pair, trader, &types.Position{ + perpKeeper.PositionsState(ctx).Set(pair, trader, &types.Position{ TraderAddress: trader.String(), Pair: pair, Size_: sdk.NewDec(1_000), @@ -279,7 +279,7 @@ func TestRemoveMargin(t *testing.T) { }) t.Log("Set position a healthy position that has 0 unrealized funding") - perpKeeper.SetPosition(ctx, pair, traderAddr, &types.Position{ + perpKeeper.PositionsState(ctx).Set(pair, traderAddr, &types.Position{ TraderAddress: traderAddr.String(), Pair: pair, Size_: sdk.NewDec(1_000), @@ -348,7 +348,7 @@ func TestRemoveMargin(t *testing.T) { }) t.Log("Set position a healthy position that has 0 unrealized funding") - perpKeeper.SetPosition(ctx, pair, traderAddr, &types.Position{ + perpKeeper.PositionsState(ctx).Set(pair, traderAddr, &types.Position{ TraderAddress: traderAddr.String(), Pair: pair, Size_: sdk.NewDec(1_000), @@ -393,7 +393,7 @@ func TestRemoveMargin(t *testing.T) { FundingPayment: res.FundingPayment, }) - pos, err := perpKeeper.GetPosition(ctx, pair, traderAddr) + pos, err := perpKeeper.PositionsState(ctx).Get(pair, traderAddr) require.NoError(t, err) assert.EqualValues(t, sdk.NewDec(400).String(), pos.Margin.String()) assert.EqualValues(t, sdk.NewDec(1000).String(), pos.Size_.String()) @@ -472,7 +472,7 @@ func TestAddMargin(t *testing.T) { } t.Log("set a position") - perpKeeper.SetPosition(ctx, assetPair, traderAddr, &types.Position{ + perpKeeper.PositionsState(ctx).Set(assetPair, traderAddr, &types.Position{ TraderAddress: traderAddr.String(), Pair: assetPair, Size_: sdk.NewDec(1_000), @@ -522,7 +522,7 @@ func TestAddMargin(t *testing.T) { }) t.Log("set position") - perpKeeper.SetPosition(ctx, assetPair, traderAddr, &types.Position{ + perpKeeper.PositionsState(ctx).Set(assetPair, traderAddr, &types.Position{ TraderAddress: traderAddr.String(), Pair: assetPair, Size_: sdk.NewDec(1_000), @@ -589,7 +589,7 @@ func TestAddMargin(t *testing.T) { }) t.Log("set position") - perpKeeper.SetPosition(ctx, assetPair, traderAddr, &types.Position{ + perpKeeper.PositionsState(ctx).Set(assetPair, traderAddr, &types.Position{ TraderAddress: traderAddr.String(), Pair: assetPair, Size_: sdk.NewDec(1_000), @@ -618,7 +618,7 @@ func TestAddMargin(t *testing.T) { assert.EqualValues(t, ctx.BlockHeight(), resp.Position.BlockNumber) t.Log("assert correct final position in state") - pos, err := perpKeeper.GetPosition(ctx, assetPair, traderAddr) + pos, err := perpKeeper.PositionsState(ctx).Get(assetPair, traderAddr) require.NoError(t, err) assert.EqualValues(t, sdk.NewDec(599).String(), pos.Margin.String()) assert.EqualValues(t, sdk.NewDec(1000).String(), pos.Size_.String()) @@ -644,3 +644,478 @@ func TestAddMargin(t *testing.T) { }) } } + +func TestGetPositionNotionalAndUnrealizedPnl(t *testing.T) { + tests := []struct { + name string + initialPosition types.Position + setMocks func(ctx sdk.Context, mocks mockedDependencies) + pnlCalcOption types.PnLCalcOption + expectedPositionalNotional sdk.Dec + expectedUnrealizedPnL sdk.Dec + }{ + { + name: "long position; positive pnl; spot price calc", + initialPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetPrice( + ctx, + common.PairBTCStable, + vpooltypes.Direction_ADD_TO_POOL, + sdk.NewDec(10), + ). + Return(sdk.NewDec(20), nil) + }, + pnlCalcOption: types.PnLCalcOption_SPOT_PRICE, + expectedPositionalNotional: sdk.NewDec(20), + expectedUnrealizedPnL: sdk.NewDec(10), + }, + { + name: "long position; negative pnl; spot price calc", + initialPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetPrice( + ctx, + common.PairBTCStable, + vpooltypes.Direction_ADD_TO_POOL, + sdk.NewDec(10), + ). + Return(sdk.NewDec(5), nil) + }, + pnlCalcOption: types.PnLCalcOption_SPOT_PRICE, + expectedPositionalNotional: sdk.NewDec(5), + expectedUnrealizedPnL: sdk.NewDec(-5), + }, + { + name: "long position; positive pnl; twap calc", + initialPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetTWAP( + ctx, + common.PairBTCStable, + vpooltypes.Direction_ADD_TO_POOL, + sdk.NewDec(10), + 15*time.Minute, + ). + Return(sdk.NewDec(20), nil) + }, + pnlCalcOption: types.PnLCalcOption_TWAP, + expectedPositionalNotional: sdk.NewDec(20), + expectedUnrealizedPnL: sdk.NewDec(10), + }, + { + name: "long position; negative pnl; twap calc", + initialPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetTWAP( + ctx, + common.PairBTCStable, + vpooltypes.Direction_ADD_TO_POOL, + sdk.NewDec(10), + 15*time.Minute, + ). + Return(sdk.NewDec(5), nil) + }, + pnlCalcOption: types.PnLCalcOption_TWAP, + expectedPositionalNotional: sdk.NewDec(5), + expectedUnrealizedPnL: sdk.NewDec(-5), + }, + { + name: "long position; positive pnl; oracle calc", + initialPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + mocks.mockVpoolKeeper.EXPECT(). + GetUnderlyingPrice( + ctx, + common.PairBTCStable, + ). + Return(sdk.NewDec(2), nil) + }, + pnlCalcOption: types.PnLCalcOption_ORACLE, + expectedPositionalNotional: sdk.NewDec(20), + expectedUnrealizedPnL: sdk.NewDec(10), + }, + { + name: "long position; negative pnl; oracle calc", + initialPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + mocks.mockVpoolKeeper.EXPECT(). + GetUnderlyingPrice( + ctx, + common.PairBTCStable, + ). + Return(sdk.MustNewDecFromStr("0.5"), nil) + }, + pnlCalcOption: types.PnLCalcOption_ORACLE, + expectedPositionalNotional: sdk.NewDec(5), + expectedUnrealizedPnL: sdk.NewDec(-5), + }, + { + name: "short position; positive pnl; spot price calc", + initialPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(-10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetPrice( + ctx, + common.PairBTCStable, + vpooltypes.Direction_REMOVE_FROM_POOL, + sdk.NewDec(10), + ). + Return(sdk.NewDec(5), nil) + }, + pnlCalcOption: types.PnLCalcOption_SPOT_PRICE, + expectedPositionalNotional: sdk.NewDec(5), + expectedUnrealizedPnL: sdk.NewDec(5), + }, + { + name: "short position; negative pnl; spot price calc", + initialPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(-10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetPrice( + ctx, + common.PairBTCStable, + vpooltypes.Direction_REMOVE_FROM_POOL, + sdk.NewDec(10), + ). + Return(sdk.NewDec(20), nil) + }, + pnlCalcOption: types.PnLCalcOption_SPOT_PRICE, + expectedPositionalNotional: sdk.NewDec(20), + expectedUnrealizedPnL: sdk.NewDec(-10), + }, + { + name: "short position; positive pnl; twap calc", + initialPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(-10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetTWAP( + ctx, + common.PairBTCStable, + vpooltypes.Direction_REMOVE_FROM_POOL, + sdk.NewDec(10), + 15*time.Minute, + ). + Return(sdk.NewDec(5), nil) + }, + pnlCalcOption: types.PnLCalcOption_TWAP, + expectedPositionalNotional: sdk.NewDec(5), + expectedUnrealizedPnL: sdk.NewDec(5), + }, + { + name: "short position; negative pnl; twap calc", + initialPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(-10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetTWAP( + ctx, + common.PairBTCStable, + vpooltypes.Direction_REMOVE_FROM_POOL, + sdk.NewDec(10), + 15*time.Minute, + ). + Return(sdk.NewDec(20), nil) + }, + pnlCalcOption: types.PnLCalcOption_TWAP, + expectedPositionalNotional: sdk.NewDec(20), + expectedUnrealizedPnL: sdk.NewDec(-10), + }, + { + name: "short position; positive pnl; oracle calc", + initialPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(-10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + mocks.mockVpoolKeeper.EXPECT(). + GetUnderlyingPrice( + ctx, + common.PairBTCStable, + ). + Return(sdk.MustNewDecFromStr("0.5"), nil) + }, + pnlCalcOption: types.PnLCalcOption_ORACLE, + expectedPositionalNotional: sdk.NewDec(5), + expectedUnrealizedPnL: sdk.NewDec(5), + }, + { + name: "long position; negative pnl; oracle calc", + initialPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(-10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + mocks.mockVpoolKeeper.EXPECT(). + GetUnderlyingPrice( + ctx, + common.PairBTCStable, + ). + Return(sdk.NewDec(2), nil) + }, + pnlCalcOption: types.PnLCalcOption_ORACLE, + expectedPositionalNotional: sdk.NewDec(20), + expectedUnrealizedPnL: sdk.NewDec(-10), + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + perpKeeper, mocks, ctx := getKeeper(t) + + tc.setMocks(ctx, mocks) + + positionalNotional, unrealizedPnl, err := perpKeeper. + getPositionNotionalAndUnrealizedPnL( + ctx, + tc.initialPosition, + tc.pnlCalcOption, + ) + require.NoError(t, err) + + assert.EqualValues(t, tc.expectedPositionalNotional, positionalNotional) + assert.EqualValues(t, tc.expectedUnrealizedPnL, unrealizedPnl) + }) + } +} + +func TestGetPreferencePositionNotionalAndUnrealizedPnl(t *testing.T) { + // all tests are assumed long positions with positive pnl for ease of calculation + // short positions and negative pnl are implicitly correct because of + // TestGetPositionNotionalAndUnrealizedPnl + testcases := []struct { + name string + initPosition types.Position + setMocks func(ctx sdk.Context, mocks mockedDependencies) + pnlPreferenceOption types.PnLPreferenceOption + expectedPositionalNotional sdk.Dec + expectedUnrealizedPnl sdk.Dec + }{ + { + name: "max pnl, pick spot price", + initPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + t.Log("Mock vpool spot price") + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetPrice( + ctx, + common.PairBTCStable, + vpooltypes.Direction_ADD_TO_POOL, + sdk.NewDec(10), + ). + Return(sdk.NewDec(20), nil) + t.Log("Mock vpool twap") + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetTWAP( + ctx, + common.PairBTCStable, + vpooltypes.Direction_ADD_TO_POOL, + sdk.NewDec(10), + 15*time.Minute, + ). + Return(sdk.NewDec(15), nil) + }, + pnlPreferenceOption: types.PnLPreferenceOption_MAX, + expectedPositionalNotional: sdk.NewDec(20), + expectedUnrealizedPnl: sdk.NewDec(10), + }, + { + name: "max pnl, pick twap", + initPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + t.Log("Mock vpool spot price") + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetPrice( + ctx, + common.PairBTCStable, + vpooltypes.Direction_ADD_TO_POOL, + sdk.NewDec(10), + ). + Return(sdk.NewDec(20), nil) + t.Log("Mock vpool twap") + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetTWAP( + ctx, + common.PairBTCStable, + vpooltypes.Direction_ADD_TO_POOL, + sdk.NewDec(10), + 15*time.Minute, + ). + Return(sdk.NewDec(30), nil) + }, + pnlPreferenceOption: types.PnLPreferenceOption_MAX, + expectedPositionalNotional: sdk.NewDec(30), + expectedUnrealizedPnl: sdk.NewDec(20), + }, + { + name: "min pnl, pick spot price", + initPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + t.Log("Mock vpool spot price") + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetPrice( + ctx, + common.PairBTCStable, + vpooltypes.Direction_ADD_TO_POOL, + sdk.NewDec(10), + ). + Return(sdk.NewDec(20), nil) + t.Log("Mock vpool twap") + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetTWAP( + ctx, + common.PairBTCStable, + vpooltypes.Direction_ADD_TO_POOL, + sdk.NewDec(10), + 15*time.Minute, + ). + Return(sdk.NewDec(30), nil) + }, + pnlPreferenceOption: types.PnLPreferenceOption_MIN, + expectedPositionalNotional: sdk.NewDec(20), + expectedUnrealizedPnl: sdk.NewDec(10), + }, + { + name: "min pnl, pick twap", + initPosition: types.Position{ + TraderAddress: sample.AccAddress().String(), + Pair: common.PairBTCStable, + Size_: sdk.NewDec(10), + OpenNotional: sdk.NewDec(10), + Margin: sdk.NewDec(1), + }, + setMocks: func(ctx sdk.Context, mocks mockedDependencies) { + t.Log("Mock vpool spot price") + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetPrice( + ctx, + common.PairBTCStable, + vpooltypes.Direction_ADD_TO_POOL, + sdk.NewDec(10), + ). + Return(sdk.NewDec(20), nil) + t.Log("Mock vpool twap") + mocks.mockVpoolKeeper.EXPECT(). + GetBaseAssetTWAP( + ctx, + common.PairBTCStable, + vpooltypes.Direction_ADD_TO_POOL, + sdk.NewDec(10), + 15*time.Minute, + ). + Return(sdk.NewDec(15), nil) + }, + pnlPreferenceOption: types.PnLPreferenceOption_MIN, + expectedPositionalNotional: sdk.NewDec(15), + expectedUnrealizedPnl: sdk.NewDec(5), + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + perpKeeper, mocks, ctx := getKeeper(t) + + tc.setMocks(ctx, mocks) + + positionalNotional, unrealizedPnl, err := perpKeeper. + getPreferencePositionNotionalAndUnrealizedPnL( + ctx, + tc.initPosition, + tc.pnlPreferenceOption, + ) + + require.NoError(t, err) + assert.EqualValues(t, tc.expectedPositionalNotional, positionalNotional) + assert.EqualValues(t, tc.expectedUnrealizedPnl, unrealizedPnl) + }) + } +} diff --git a/x/perp/keeper/perp.go b/x/perp/keeper/perp.go index 6ff9951d7..e3d90606e 100644 --- a/x/perp/keeper/perp.go +++ b/x/perp/keeper/perp.go @@ -3,27 +3,9 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/NibiruChain/nibiru/x/common" "github.com/NibiruChain/nibiru/x/perp/types" ) -// TODO test: ClearPosition | https://github.com/NibiruChain/nibiru/issues/299 -func (k Keeper) ClearPosition(ctx sdk.Context, pair common.AssetPair, traderAddr sdk.AccAddress) error { - return k.PositionsState(ctx).Delete(pair, traderAddr) -} - -func (k Keeper) GetPosition( - ctx sdk.Context, pair common.AssetPair, traderAddr sdk.AccAddress, -) (*types.Position, error) { - return k.PositionsState(ctx).Get(pair, traderAddr) -} - -func (k Keeper) SetPosition( - ctx sdk.Context, pair common.AssetPair, traderAddr sdk.AccAddress, - position *types.Position) { - k.PositionsState(ctx).Set(pair, traderAddr, position) -} - // SettlePosition settles a trader position func (k Keeper) SettlePosition( ctx sdk.Context, @@ -39,19 +21,17 @@ func (k Keeper) SettlePosition( return sdk.NewCoins(), nil } - err = k.ClearPosition( - ctx, + if err = k.PositionsState(ctx).Delete( currentPosition.Pair, traderAddr, - ) - if err != nil { - return + ); err != nil { + return sdk.NewCoins(), nil } // run calculations on settled values settlementPrice, err := k.VpoolKeeper.GetSettlementPrice(ctx, currentPosition.Pair) if err != nil { - return + return sdk.NewCoins(), nil } settledValue := sdk.ZeroDec() diff --git a/x/perp/keeper/perp_test.go b/x/perp/keeper/perp_test.go index bdb148d15..9f6246b12 100644 --- a/x/perp/keeper/perp_test.go +++ b/x/perp/keeper/perp_test.go @@ -29,8 +29,7 @@ func TestGetAndSetPosition(t *testing.T) { nibiruApp, ctx := testapp.NewNibiruAppAndContext(true) pair := common.MustNewAssetPair("osmo:nusd") - _, err := nibiruApp.PerpKeeper.GetPosition( - ctx, pair, trader) + _, err := nibiruApp.PerpKeeper.PositionsState(ctx).Get(pair, trader) require.Error(t, err) require.ErrorContains(t, err, types.ErrPositionNotFound.Error()) }, @@ -43,8 +42,7 @@ func TestGetAndSetPosition(t *testing.T) { traderAddr := sample.AccAddress() nibiruApp, ctx := testapp.NewNibiruAppAndContext(true) - _, err := nibiruApp.PerpKeeper.GetPosition( - ctx, vpoolPair, traderAddr) + _, err := nibiruApp.PerpKeeper.PositionsState(ctx).Get(vpoolPair, traderAddr) require.Error(t, err) require.ErrorContains(t, err, types.ErrPositionNotFound.Error()) @@ -54,10 +52,8 @@ func TestGetAndSetPosition(t *testing.T) { Size_: sdk.OneDec(), Margin: sdk.OneDec(), } - nibiruApp.PerpKeeper.SetPosition( - ctx, vpoolPair, traderAddr, dummyPosition) - outPosition, err := nibiruApp.PerpKeeper.GetPosition( - ctx, vpoolPair, traderAddr) + nibiruApp.PerpKeeper.PositionsState(ctx).Set(vpoolPair, traderAddr, dummyPosition) + outPosition, err := nibiruApp.PerpKeeper.PositionsState(ctx).Get(vpoolPair, traderAddr) require.NoError(t, err) require.EqualValues(t, dummyPosition, outPosition) }, @@ -72,7 +68,7 @@ func TestGetAndSetPosition(t *testing.T) { } } -func TestClearPosition(t *testing.T) { +func TestDeletePosition(t *testing.T) { testCases := []struct { name string test func() @@ -89,8 +85,7 @@ func TestClearPosition(t *testing.T) { t.Log("vpool contains no positions to start") for _, trader := range traders { - _, err := nibiruApp.PerpKeeper.GetPosition( - ctx, vpoolPair, trader) + _, err := nibiruApp.PerpKeeper.PositionsState(ctx).Get(vpoolPair, trader) require.Error(t, err) require.ErrorContains(t, err, types.ErrPositionNotFound.Error()) } @@ -103,10 +98,8 @@ func TestClearPosition(t *testing.T) { Size_: sdk.OneDec(), Margin: sdk.OneDec(), } - nibiruApp.PerpKeeper.SetPosition( - ctx, vpoolPair, traderAddr, dummyPosition) - outPosition, err := nibiruApp.PerpKeeper.GetPosition( - ctx, vpoolPair, traderAddr) + nibiruApp.PerpKeeper.PositionsState(ctx).Set(vpoolPair, traderAddr, dummyPosition) + outPosition, err := nibiruApp.PerpKeeper.PositionsState(ctx).Get(vpoolPair, traderAddr) require.NoError(t, err) require.EqualValues(t, dummyPosition, outPosition) t.Logf("position created successfully on vpool, %v, for trader %v", @@ -117,28 +110,24 @@ func TestClearPosition(t *testing.T) { t.Log("attempt to clear all positions") require.NoError(t, - nibiruApp.PerpKeeper.ClearPosition( - ctx, vpoolPair, traders[0]), + nibiruApp.PerpKeeper.PositionsState(ctx).Delete(vpoolPair, traders[0]), ) - outPosition, err := nibiruApp.PerpKeeper.GetPosition( - ctx, vpoolPair, traders[0]) + outPosition, err := nibiruApp.PerpKeeper.PositionsState(ctx).Get(vpoolPair, traders[0]) require.ErrorIs(t, err, types.ErrPositionNotFound) require.Nil(t, outPosition) - outPosition, err = nibiruApp.PerpKeeper.GetPosition( - ctx, vpoolPair, traders[1]) + outPosition, err = nibiruApp.PerpKeeper.PositionsState(ctx).Get(vpoolPair, traders[1]) require.NoError(t, err) require.EqualValues(t, dummyPositions[1], outPosition) t.Log("trader 1 has a position and trader 0 does not.") t.Log("clearing position of trader 1...") require.NoError(t, - nibiruApp.PerpKeeper.ClearPosition( - ctx, vpoolPair, traders[1]), + nibiruApp.PerpKeeper.PositionsState(ctx).Delete(vpoolPair, traders[1]), ) - outPosition, err = nibiruApp.PerpKeeper.GetPosition( - ctx, vpoolPair, traders[1]) + + outPosition, err = nibiruApp.PerpKeeper.PositionsState(ctx).Get(vpoolPair, traders[1]) require.ErrorIs(t, err, types.ErrPositionNotFound) require.Nil(t, outPosition) t.Log("Success, all trader positions have been cleared.") @@ -154,7 +143,7 @@ func TestClearPosition(t *testing.T) { } } -func TestKeeper_ClosePosition(t *testing.T) { +func TestKeeperClosePosition(t *testing.T) { // TODO(mercilex): simulate funding payments t.Run("success", func(t *testing.T) { t.Log("Setup Nibiru app, pair, and trader") diff --git a/x/perp/keeper/state.go b/x/perp/keeper/state.go index 832ca7794..ca97b7728 100644 --- a/x/perp/keeper/state.go +++ b/x/perp/keeper/state.go @@ -1,31 +1,16 @@ package keeper import ( - "context" "fmt" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" "github.com/NibiruChain/nibiru/x/common" - "github.com/NibiruChain/nibiru/x/perp/types" ) -func (k Keeper) Params( - goCtx context.Context, req *types.QueryParamsRequest, -) (*types.QueryParamsResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "invalid request") - } - ctx := sdk.UnwrapSDKContext(goCtx) - - return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil -} - func (k Keeper) PositionsState(ctx sdk.Context) PositionsState { return newPositions(ctx, k.storeKey, k.cdc) } @@ -42,33 +27,6 @@ func (k Keeper) PrepaidBadDebtState(ctx sdk.Context) PrepaidBadDebtState { return newPrepaidBadDebtState(ctx, k.storeKey, k.cdc) } -var paramsNamespace = []byte{0x0} -var paramsKey = []byte{0x0} - -type ParamsState Keeper - -func (p ParamsState) getKV(ctx sdk.Context) sdk.KVStore { - return prefix.NewStore(ctx.KVStore(p.storeKey), paramsNamespace) -} - -func (p ParamsState) Get(ctx sdk.Context) (*types.Params, error) { - kv := p.getKV(ctx) - - value := kv.Get(paramsKey) - if value == nil { - return nil, fmt.Errorf("not found") - } - - params := new(types.Params) - p.cdc.MustUnmarshal(value, params) - return params, nil -} - -func (p ParamsState) Set(ctx sdk.Context, params *types.Params) { - kv := p.getKV(ctx) - kv.Set(paramsKey, p.cdc.MustMarshal(params)) -} - var positionsNamespace = []byte{0x1} type PositionsState struct { @@ -203,6 +161,24 @@ func (p PairMetadataState) GetAll() []*types.PairMetadata { return pairMetadatas } +// getLatestCumulativePremiumFraction returns the last cumulative premium fraction recorded for the +// specific pair. +func (k Keeper) getLatestCumulativePremiumFraction( + ctx sdk.Context, pair common.AssetPair, +) (sdk.Dec, error) { + pairMetadata, err := k.PairMetadataState(ctx).Get(pair) + if err != nil { + k.Logger(ctx).Error( + err.Error(), + "pair", + pair.String(), + ) + return sdk.Dec{}, err + } + // this should never fail + return pairMetadata.CumulativePremiumFractions[len(pairMetadata.CumulativePremiumFractions)-1], nil +} + var whitelistNamespace = []byte{0x3} type WhitelistState struct { diff --git a/x/perp/keeper/state_test.go b/x/perp/keeper/state_test.go index 97b83d199..dd2146e44 100644 --- a/x/perp/keeper/state_test.go +++ b/x/perp/keeper/state_test.go @@ -73,3 +73,52 @@ func TestPairMetadata_GetAll(t *testing.T) { require.Contains(t, pairMetadatas, sm) } } + +func TestGetLatestCumulativePremiumFraction(t *testing.T) { + testCases := []struct { + name string + test func() + }{ + { + name: "happy path", + test: func() { + keeper, _, ctx := getKeeper(t) + + metadata := &types.PairMetadata{ + Pair: common.PairGovStable, + CumulativePremiumFractions: []sdk.Dec{ + sdk.NewDec(1), + sdk.NewDec(2), // returns the latest from the list + }, + } + keeper.PairMetadataState(ctx).Set(metadata) + + latestCumulativePremiumFraction, err := keeper. + getLatestCumulativePremiumFraction(ctx, common.PairGovStable) + + require.NoError(t, err) + assert.Equal(t, sdk.NewDec(2), latestCumulativePremiumFraction) + }, + }, + { + name: "uninitialized vpool has no metadata | fail", + test: func() { + perpKeeper, _, ctx := getKeeper(t) + vpool := common.AssetPair{ + Token0: "xxx", + Token1: "yyy", + } + lcpf, err := perpKeeper.getLatestCumulativePremiumFraction( + ctx, vpool) + require.Error(t, err) + assert.EqualValues(t, sdk.Dec{}, lcpf) + }, + }, + } + for _, testCase := range testCases { + tc := testCase + t.Run(tc.name, func(t *testing.T) { + tc.test() + }) + } +}