Skip to content

Commit

Permalink
feat(perp): Emit PositionChangedEvent when changing position margin (
Browse files Browse the repository at this point in the history
…#687)

* Remove debug statement

* Emit PositionChangedEvent from AddMargin

* Refactor CalcRemainMarginWithFundingPayment

* Emit PositionChangedEvent on RemoveMargin

* Update CHANGELOG.md

* Remove MarginChangedEvent

* Apply suggestions from code review

Co-authored-by: AgentSmithMatrix <[email protected]>
  • Loading branch information
NibiruHeisenberg and AgentSmithMatrix authored Jul 8, 2022
1 parent 28eb87e commit b080568
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 527 deletions.
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

0 comments on commit b080568

Please sign in to comment.