Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(perp): Emit PositionChangedEvent when changing position margin #687

Merged
merged 8 commits into from
Jul 8, 2022
Merged
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- [#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
- [#686](https://github.com/NibiruChain/nibiru/pull/686) Reorganize PerpKeeper methods

### State Machine Breaking Change

- [*687](https://github.com/NibiruChain/nibiru/pull/686) Emit `PositionChangedEvent` upon changing margin.
21 changes: 0 additions & 21 deletions proto/perp/v1/event.proto
Original file line number Diff line number Diff line change
Expand Up @@ -188,25 +188,4 @@ message PositionSettledEvent {
(gogoproto.moretags) = "yaml:\"settled_coins\"",
(gogoproto.nullable) = false
];
}

// Emitted when a position's margin is changed (AddMargin or RemoveMargin).
message MarginChangedEvent {
// Identifier for the virtual pool of the position.
string pair = 1;

// Owner of the position.
string trader_address = 2;

// Amount of margin exchanged.
string margin_amount = 3 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];

// Amount of funding payment applied.
string funding_payment = 4 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}
6 changes: 3 additions & 3 deletions x/perp/keeper/calc.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ func (k Keeper) CalcRemainMarginWithFundingPayment(
if currentPosition.Size_.IsZero() {
remaining.FundingPayment = sdk.ZeroDec()
} else {
remaining.FundingPayment = remaining.LatestCumulativePremiumFraction.
Sub(currentPosition.LastUpdateCumulativePremiumFraction).
remaining.FundingPayment = (remaining.LatestCumulativePremiumFraction.
Sub(currentPosition.LastUpdateCumulativePremiumFraction)).
Mul(currentPosition.Size_)
}

Expand Down Expand Up @@ -88,7 +88,7 @@ func (k Keeper) calcFreeCollateral(
return sdk.Dec{}, err
}

if err := k.requireVpool(ctx, pos.Pair); err != nil {
if err = k.requireVpool(ctx, pos.Pair); err != nil {
return sdk.Dec{}, err
}

Expand Down
85 changes: 63 additions & 22 deletions x/perp/keeper/margin.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (k Keeper) AddMargin(
ctx := sdk.UnwrapSDKContext(goCtx)

// validate trader
msgSender, err := sdk.AccAddressFromBech32(msg.Sender)
traderAddr, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -49,7 +49,7 @@ func (k Keeper) AddMargin(
}

// ------------- AddMargin -------------
position, err := k.PositionsState(ctx).Get(pair, msgSender)
position, err := k.PositionsState(ctx).Get(pair, traderAddr)
if err != nil {
return nil, err
}
Expand All @@ -67,22 +67,43 @@ func (k Keeper) AddMargin(

coinToSend := sdk.NewCoin(pair.GetQuoteTokenDenom(), msg.Margin.Amount)
if err = k.BankKeeper.SendCoinsFromAccountToModule(
ctx, msgSender, types.VaultModuleAccount, sdk.NewCoins(coinToSend),
ctx, traderAddr, types.VaultModuleAccount, sdk.NewCoins(coinToSend),
); err != nil {
return nil, err
}

position.Margin = remainingMargin.Margin
position.LastUpdateCumulativePremiumFraction = remainingMargin.LatestCumulativePremiumFraction
position.BlockNumber = ctx.BlockHeight()
k.PositionsState(ctx).Set(pair, msgSender, position)
k.PositionsState(ctx).Set(pair, traderAddr, position)

positionNotional, unrealizedPnl, err := k.getPositionNotionalAndUnrealizedPnL(ctx, *position, types.PnLCalcOption_SPOT_PRICE)
if err != nil {
return nil, err
}

spotPrice, err := k.VpoolKeeper.GetSpotPrice(ctx, pair)
if err != nil {
return nil, err
}

err = ctx.EventManager().EmitTypedEvent(
&types.MarginChangedEvent{
Pair: pair.String(),
TraderAddress: msgSender.String(),
MarginAmount: msg.Margin.Amount,
FundingPayment: remainingMargin.FundingPayment,
&types.PositionChangedEvent{
Pair: pair.String(),
TraderAddress: traderAddr.String(),
Margin: sdk.NewCoin(pair.GetQuoteTokenDenom(), position.Margin.RoundInt()),
PositionNotional: positionNotional,
ExchangedPositionSize: sdk.ZeroDec(), // always zero when adding margin
TransactionFee: sdk.NewCoin(pair.GetQuoteTokenDenom(), sdk.ZeroInt()), // always zero when adding margin
PositionSize: position.Size_,
RealizedPnl: sdk.ZeroDec(), // always zero when adding margin
UnrealizedPnlAfter: unrealizedPnl,
BadDebt: remainingMargin.BadDebt, // always zero when adding margin
FundingPayment: remainingMargin.FundingPayment,
SpotPrice: spotPrice,
BlockHeight: ctx.BlockHeight(),
BlockTimeMs: ctx.BlockTime().UnixMilli(),
LiquidationPenalty: sdk.ZeroDec(),
},
)

Expand Down Expand Up @@ -138,8 +159,7 @@ func (k Keeper) RemoveMargin(
}

marginDelta := msg.Margin.Amount.Neg()
remainingMargin, err := k.CalcRemainMarginWithFundingPayment(
ctx, *position, marginDelta.ToDec())
remainingMargin, err := k.CalcRemainMarginWithFundingPayment(ctx, *position, marginDelta.ToDec())
if err != nil {
return nil, err
}
Expand All @@ -151,28 +171,49 @@ func (k Keeper) RemoveMargin(
position.LastUpdateCumulativePremiumFraction = remainingMargin.LatestCumulativePremiumFraction
freeCollateral, err := k.calcFreeCollateral(ctx, *position)
if err != nil {
return res, err
return nil, err
} else if !freeCollateral.IsPositive() {
return res, fmt.Errorf("not enough free collateral")
return nil, fmt.Errorf("not enough free collateral")
}

k.PositionsState(ctx).Set(pair, traderAddr, position)

coinToSend := sdk.NewCoin(pair.GetQuoteTokenDenom(), msg.Margin.Amount)
err = k.Withdraw(ctx, pair.GetQuoteTokenDenom(), traderAddr, msg.Margin.Amount)
positionNotional, unrealizedPnl, err := k.getPositionNotionalAndUnrealizedPnL(ctx, *position, types.PnLCalcOption_SPOT_PRICE)
if err != nil {
return nil, err
}

err = ctx.EventManager().EmitTypedEvent(&types.MarginChangedEvent{
Pair: pair.String(),
TraderAddress: traderAddr.String(),
MarginAmount: msg.Margin.Amount,
FundingPayment: remainingMargin.FundingPayment,
})
spotPrice, err := k.VpoolKeeper.GetSpotPrice(ctx, pair)
if err != nil {
return nil, err
}

if err = k.Withdraw(ctx, pair.GetQuoteTokenDenom(), traderAddr, msg.Margin.Amount); err != nil {
return nil, err
}

err = ctx.EventManager().EmitTypedEvent(
&types.PositionChangedEvent{
Pair: pair.String(),
TraderAddress: traderAddr.String(),
Margin: sdk.NewCoin(pair.GetQuoteTokenDenom(), position.Margin.RoundInt()),
PositionNotional: positionNotional,
ExchangedPositionSize: sdk.ZeroDec(), // always zero when removing margin
TransactionFee: sdk.NewCoin(pair.GetQuoteTokenDenom(), sdk.ZeroInt()), // always zero when removing margin
PositionSize: position.Size_,
RealizedPnl: sdk.ZeroDec(), // always zero when removing margin
UnrealizedPnlAfter: unrealizedPnl,
BadDebt: remainingMargin.BadDebt, // always zero when removing margin
FundingPayment: remainingMargin.FundingPayment,
SpotPrice: spotPrice,
BlockHeight: ctx.BlockHeight(),
BlockTimeMs: ctx.BlockTime().UnixMilli(),
LiquidationPenalty: sdk.ZeroDec(),
},
)

return &types.MsgRemoveMarginResponse{
MarginOut: coinToSend,
MarginOut: sdk.NewCoin(pair.GetQuoteTokenDenom(), msg.Margin.Amount),
FundingPayment: remainingMargin.FundingPayment,
}, err
}
Expand Down
48 changes: 27 additions & 21 deletions x/perp/keeper/margin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,35 +464,28 @@ func TestRemoveMargin(t *testing.T) {
t.Log("Set vpool defined by pair on PerpKeeper")
perpKeeper := &nibiruApp.PerpKeeper
perpKeeper.PairMetadataState(ctx).Set(&types.PairMetadata{
Pair: pair,
CumulativePremiumFractions: []sdk.Dec{
sdk.ZeroDec(),
sdk.MustNewDecFromStr("0.1")},
Pair: pair,
CumulativePremiumFractions: []sdk.Dec{sdk.ZeroDec()},
})

t.Log("increment block height and time for twap calculation")
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1).
WithBlockTime(time.Now().Add(time.Minute))

t.Log("Fund trader account with sufficient quote")

err := simapp.FundAccount(nibiruApp.BankKeeper, ctx, traderAddr,
sdk.NewCoins(
sdk.NewInt64Coin("yyy", 66),
))
require.NoError(t, err)
require.NoError(t, simapp.FundAccount(nibiruApp.BankKeeper, ctx, traderAddr,
sdk.NewCoins(sdk.NewInt64Coin("yyy", 60))),
)

t.Log("Open long position with 5x leverage")
side := types.Side_BUY
quote := sdk.NewInt(60)
leverage := sdk.NewDec(5)
baseLimit := sdk.NewInt(10)
err = nibiruApp.PerpKeeper.OpenPosition(
ctx, pair, side, traderAddr, quote, leverage, baseLimit.ToDec())
require.NoError(t, err)
baseLimit := sdk.ZeroDec()
require.NoError(t, perpKeeper.OpenPosition(ctx, pair, side, traderAddr, quote, leverage, baseLimit))

t.Log("Position should be accessible following 'OpenPosition'")
_, err = nibiruApp.PerpKeeper.PositionsState(ctx).Get(pair, traderAddr)
_, err := nibiruApp.PerpKeeper.PositionsState(ctx).Get(pair, traderAddr)
require.NoError(t, err)

t.Log("Verify correct events emitted for 'OpenPosition'")
Expand All @@ -513,12 +506,25 @@ func TestRemoveMargin(t *testing.T) {
assert.EqualValues(t, sdk.ZeroDec(), res.FundingPayment)

t.Log("Verify correct events emitted for 'RemoveMargin'")
testutilevents.RequireHasTypedEvent(t, ctx, &types.MarginChangedEvent{
Pair: pair.String(),
TraderAddress: traderAddr.String(),
MarginAmount: msg.Margin.Amount,
FundingPayment: res.FundingPayment,
})
testutilevents.RequireContainsTypedEvent(t, ctx,
&types.PositionChangedEvent{
Pair: msg.TokenPair,
TraderAddress: traderAddr.String(),
Margin: sdk.NewInt64Coin(pair.GetQuoteTokenDenom(), 54),
PositionNotional: sdk.NewDec(300),
ExchangedPositionSize: sdk.ZeroDec(), // always zero when removing margin
TransactionFee: sdk.NewCoin(pair.GetQuoteTokenDenom(), sdk.ZeroInt()), // always zero when removing margin
PositionSize: sdk.MustNewDecFromStr("299.910026991902429271"),
RealizedPnl: sdk.ZeroDec(), // always zero when removing margin
UnrealizedPnlAfter: sdk.ZeroDec(),
BadDebt: sdk.ZeroDec(), // always zero when removing margin
FundingPayment: sdk.ZeroDec(),
SpotPrice: sdk.MustNewDecFromStr("1.00060009"),
BlockHeight: ctx.BlockHeight(),
BlockTimeMs: ctx.BlockTime().UnixMilli(),
LiquidationPenalty: sdk.ZeroDec(),
},
)
},
},
}
Expand Down
Loading