Skip to content

Commit

Permalink
feat, cli (perp): MsgLiquidate (#426)
Browse files Browse the repository at this point in the history
* wip: Add liquidate initial code

* feat: Add liquidation fee to params

* feat: Allow multiple margin ratio calculation method

* wip: Evaluate position to liquidate

* test: test commit

* feat: Add partial liquidation ratio

* wip: squeletton for liquidation

* feat: Add liquidation keeper function

* feat: Add is over spread limit initial function

* fix: Minor issue in error comments

* fix: Fix calls in tests for new create pool parameter

* feat: Add withdraw and send to IF

* fix: Usemax oracle spread ratio in vpool

* fix: Fix test failing because mustnewdec annoyingly need a left 0

* feat: Add initial test for liquidate (wip)

* feat: ADd happy path test for no liquidaiton done

* fix: Adujust max base on short

* Pass oldPosition into keeper methods

Also refactored trader to sdk.AccAddress

* Add tests for GetPositionNotionalAndUnrealizedPnl

* Delete interfaces.go

* Add tests for getPreferencePositionNotionalAndUnrealizedPnL

* Add comments for getPositionNotionalAndUnrealizedPnL

* Test GetPositionNotionalAndUnrealizedPnl with ORACLE prices

* wip: Error on the set params

* fix: Fix casing for params

* test: boilerplate test for createLiquidation test

* fix: Fix module name issue in the testing function

* tests: Add test for create liquidation

* tests: Test individual close position functions

* fix: SetParams panics due to missing transient store

* fix (margin.go): bug in GetMarginRatio

* fix, #wip

* fix: Add send module to module keeper

* feat: Happy path for margin high enough

* fix wip: almost done with happy liquidate path

* fix: liquidate happy path runs

* test (liquidate): uncomment margin ok test case

* refactor (liquidate.go): Prevent transfers with zero amount inputs

* refactor (liquidate.go): Build output at end fo fn in CreateLiquidation

* feat (perp): Liquidate proto rpc method with passing tests

* cli (perp): LiquidateCmd connected to app

* rename liquidation_test.go -> liquidate_unit_test.go

* refactor, tests: Make types consistent on liquidate proto message. (2)
ValidateBasic tests

* Update x/perp/spec/02_msgs_and_client.md

Co-authored-by: Walter White <101130700+NibiruHeisenberg@users.noreply.github.com>

Co-authored-by: Matthias Darblade <matthias.darblade@gmail.com>
Co-authored-by: MD <matthias@matrixsystems.co>
Co-authored-by: Mat-Cosmos <97468149+matthiasmatt@users.noreply.github.com>
Co-authored-by: Walter White <heisenberg@matrixsystems.co>
Co-authored-by: Walter White <101130700+MatrixHeisenberg@users.noreply.github.com>
Co-authored-by: Walter White <101130700+NibiruHeisenberg@users.noreply.github.com>
  • Loading branch information
7 people authored May 27, 2022
1 parent b691edf commit 47e1ed8
Show file tree
Hide file tree
Showing 17 changed files with 428 additions and 76 deletions.
9 changes: 5 additions & 4 deletions proto/perp/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ service Msg {

/* Liquidate is a transaction that allows the caller to fully or partially
liquidate an existing position. */
// rpc Liquidate(MsgLiquidate) returns (MsgLiquidateResponse) {
// option (google.api.http).post = "/nibiru/perp/liquidate";
// }
rpc Liquidate(MsgLiquidate) returns (MsgLiquidateResponse) {
option (google.api.http).post = "/nibiru/perp/liquidate";
}

rpc OpenPosition(MsgOpenPosition) returns (MsgOpenPositionResponse) {
option (google.api.http).post = "/nibiru/perp/open_position";
Expand Down Expand Up @@ -77,7 +77,8 @@ message MsgLiquidate {
// TokenPair is the identifier for the position's virtual pool
string token_pair = 2;
// Trader is the address of the owner of the position
string trader = 3;
bytes trader = 3 [
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
}

message MsgLiquidateResponse {
Expand Down
47 changes: 45 additions & 2 deletions x/perp/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func GetTxCmd() *cobra.Command {
txCmd.AddCommand(
RemoveMarginCmd(),
AddMarginCmd(),
LiquidateCmd(),
OpenPositionCmd(),
)

Expand Down Expand Up @@ -109,7 +110,7 @@ func RemoveMarginCmd() *cobra.Command {
Short: "Removes margin from a position, decreasing its margin ratio",
Long: strings.TrimSpace(
fmt.Sprintf(`
$ %s tx perp remove-margin osmo-nusd 100nusd
$ %s tx perp remove-margin osmo:nusd 100nusd
`, version.AppName),
),
Args: cobra.ExactArgs(2),
Expand Down Expand Up @@ -151,7 +152,7 @@ func AddMarginCmd() *cobra.Command {
Short: "Adds margin to a position, increasing its margin ratio",
Long: strings.TrimSpace(
fmt.Sprintf(`
$ %s tx perp add-margin osmo-nusd 100nusd
$ %s tx perp add-margin osmo:nusd 100nusd
`, version.AppName),
),
Args: cobra.ExactArgs(2),
Expand Down Expand Up @@ -186,3 +187,45 @@ func AddMarginCmd() *cobra.Command {

return cmd
}

func LiquidateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "liquidate [vpool] [trader]",
Short: "liquidates the position of 'trader' on 'vpool' if possible",
Long: strings.TrimSpace(
fmt.Sprintf(`
$ %s tx perp liquidate osmo:nusd nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl
`, version.AppName),
),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()).WithTxConfig(
clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever)

traderAddr, err := sdk.AccAddressFromBech32(args[1])
if err != nil {
return err
}

msg := &types.MsgLiquidate{
Sender: clientCtx.GetFromAddress(),
TokenPair: args[0],
Trader: traderAddr,
}
if err = msg.ValidateBasic(); err != nil {
return err
}

return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}
4 changes: 2 additions & 2 deletions x/perp/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ Args:
func EmitPositionLiquidate(
ctx sdk.Context,
vpool string,
owner sdk.AccAddress,
trader sdk.AccAddress,
notional sdk.Dec,
vsize sdk.Dec,
liquidator sdk.AccAddress,
Expand All @@ -170,7 +170,7 @@ func EmitPositionLiquidate(
badDebt sdk.Dec,
) {
ctx.EventManager().EmitEvent(NewPositionLiquidateEvent(
vpool, owner, notional, vsize, liquidator, feeToLiquidator, feeToPerpEF,
vpool, trader, notional, vsize, liquidator, feeToLiquidator, feeToPerpEF,
badDebt,
))
}
Expand Down
3 changes: 3 additions & 0 deletions x/perp/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
case *types.MsgOpenPosition:
res, err := msgServer.OpenPosition(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgLiquidate:
res, err := msgServer.Liquidate(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
default:
errMsg := fmt.Sprintf(
"unrecognized %s message type: %T", types.ModuleName, msg)
Expand Down
1 change: 1 addition & 0 deletions x/perp/keeper/clearing_house_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down
7 changes: 3 additions & 4 deletions x/perp/keeper/liquidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ func (k Keeper) Liquidate(
}

// validate trader (msg.PositionOwner)
trader, err := sdk.AccAddressFromBech32(msg.Trader)
if err != nil {
if err = sdk.VerifyAddressFormat(msg.Trader); err != nil {
return res, err
}

Expand All @@ -42,7 +41,7 @@ func (k Keeper) Liquidate(
return res, err
}

position, err := k.GetPosition(ctx, pair, trader)
position, err := k.GetPosition(ctx, pair, msg.Trader)
if err != nil {
return res, err
}
Expand Down Expand Up @@ -87,7 +86,7 @@ func (k Keeper) Liquidate(
events.EmitPositionLiquidate(
/* ctx */ ctx,
/* vpool */ pair.String(),
/* owner */ trader,
/* owner */ msg.Trader,
/* notional */ liquidationResponse.PositionResp.ExchangedQuoteAssetAmount,
/* vsize */ liquidationResponse.PositionResp.ExchangedPositionSize,
/* liquidator */ msg.Sender,
Expand Down
1 change: 1 addition & 0 deletions x/perp/keeper/liquidate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand Down
23 changes: 18 additions & 5 deletions x/perp/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,26 @@ type msgServer struct {
k Keeper
}

var _ types.MsgServer = msgServer{}

// NewMsgServerImpl returns an implementation of the MsgServer interface
// for the provided Keeper.
func NewMsgServerImpl(keeper Keeper) types.MsgServer {
return &msgServer{k: keeper}
}

var _ types.MsgServer = msgServer{}

func (k msgServer) RemoveMargin(ctx context.Context, margin *types.MsgRemoveMargin) (*types.MsgRemoveMarginResponse, error) {
func (k msgServer) RemoveMargin(ctx context.Context, margin *types.MsgRemoveMargin,
) (*types.MsgRemoveMarginResponse, error) {
return k.k.RemoveMargin(ctx, margin)
}

func (k msgServer) AddMargin(ctx context.Context, margin *types.MsgAddMargin) (*types.MsgAddMarginResponse, error) {
func (k msgServer) AddMargin(ctx context.Context, margin *types.MsgAddMargin,
) (*types.MsgAddMarginResponse, error) {
return k.k.AddMargin(ctx, margin)
}

func (k msgServer) OpenPosition(goCtx context.Context, req *types.MsgOpenPosition) (*types.MsgOpenPositionResponse, error) {
func (k msgServer) OpenPosition(goCtx context.Context, req *types.MsgOpenPosition,
) (*types.MsgOpenPositionResponse, error) {
pair, err := common.NewTokenPairFromStr(req.TokenPair)
if err != nil {
panic(err) // must not happen
Expand All @@ -53,3 +56,13 @@ func (k msgServer) OpenPosition(goCtx context.Context, req *types.MsgOpenPositio

return &types.MsgOpenPositionResponse{}, nil
}

func (k msgServer) Liquidate(goCtx context.Context, msg *types.MsgLiquidate,
) (*types.MsgLiquidateResponse, error) {
response, err := k.k.Liquidate(goCtx, msg)
if err != nil {
return nil, err
}

return response, nil
}
6 changes: 3 additions & 3 deletions x/perp/spec/01_liquidate.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ From there, we compute a margin ratio using only the spot price. This margin rat

- When this margin ratio is higher than the liqudiation fee, we close the position and transfer the fees.
- Half the fees are sent to the liquidator
- The other half is sent to the insurance fund.
- The other half is sent to the ecosystem fund.

- Otherwise, we compute the margin ratio including the funding payments in the unrealizedPnL to see if we can still pay the liquidation fee
- If we can pay the liquidation fee to the liquidator, we send the remaining margin to the insurance fund
- Otherwise, we count is as bad debt added onto the position potentially withdraw from the insuranceFund to the pre-paid vault to pay the trader.
- If we can pay the liquidation fee to the liquidator, we send the remaining margin to the ecosystem fund
- Otherwise, we count is as bad debt added onto the position potentially withdraw from the ecosystemFund to the pre-paid vault to pay the trader.
76 changes: 76 additions & 0 deletions x/perp/spec/02_msgs_and_client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Messages and Client <!-- omit in toc -->

This page describes the message (`Msg`) structures and expected state transitions that these messages bring about when wrapped in transactions. These descriptions are accompanied by documentation for their corresponding CLI commands.

- [OpenPosition](#openposition)
- [ClosePosition](#closeposition)
- [AddMargin](#addmargin)
- [RemoveMargin](#removemargin)
- [Liquidate](#liquidate)

## OpenPosition

`OpenPosition` defines a method for opening or altering a new position, which sends funds the vault to the trader, realizing any outstanding profits and losses (PnL), funding payments, and bad debt.

#### `OpenPosition` CLI command:
// TODO:
```sh
nibid tx perp open-perp --vpool --side --margin --leverage --base-limit
```

This command has several required flags:
- `vpool`: Identifier for the position's virtual pool.
- `side`: Either "long" or "short"
- `margin`: The amount of collateral input to back the position. This collateral is the quote asset of the 'vpool'.
- `leverage`: A decimal number between 1 and 10 (inclusive) that specifies how much leverage the trader wishes to take on.
- `base-limit`: Limiter to ensure the trader doesn't get screwed by slippage.


## ClosePosition

`ClosePosition` defines a method for closing a trader's position, which sends funds the vault to the trader, realizing any outstanding profits and losses (PnL), funding payments, and bad debt.

#### `ClosePosition` CLI command:

```sh
nibid tx perp close-perp [vpool]
```

## AddMargin

`AddMargin` deleverages a trader's position by adding margin to it without altering its notional value. Adding margin increases the margin ratio of the position.

```go
type MsgAddMargin struct {
// Sender: sdk.AccAddress of the owner of the position
Sender string
// TokenPair: identifier for the position's virtual pool
TokenPair string
// Margin: Amount of margin (quote units) to add to the position
Margin sdk.Coin
}
```

#### `AddMargin` CLI command:

```sh
nibid tx perp add-margin [vpool] [margin]
```

## RemoveMargin

`RemoveMargin` further leverages a trader's position by removing some of the margin that backs it without altering its notional value. Removing margin decreases the margin ratio of the position and increases the risk of liquidation.

#### `RemoveMargin` CLI command:

```sh
nibid tx perp remove-margin [vpool] [margin]
# example
nibid tx perp remove-margin atom:nusd 100nusd
```

## Liquidate

```sh
nibid tx perp liquidate [vpool] [trader]
```
3 changes: 3 additions & 0 deletions x/perp/spec/03_state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# State

Describes the structures expected to be marshalled into the store and their keys.
1 change: 1 addition & 0 deletions x/perp/spec/04_events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Events
2 changes: 2 additions & 0 deletions x/perp/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
func RegisterCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&MsgRemoveMargin{}, "perp/remove_margin", nil)
cdc.RegisterConcrete(&MsgAddMargin{}, "perp/add_margin", nil)
cdc.RegisterConcrete(&MsgLiquidate{}, "perp/liquidate", nil)
}

func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
Expand All @@ -18,6 +19,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
/* implementations */
&MsgRemoveMargin{},
&MsgAddMargin{},
&MsgLiquidate{},
&MsgOpenPosition{},
)

Expand Down
29 changes: 29 additions & 0 deletions x/perp/types/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

var _ sdk.Msg = &MsgRemoveMargin{}
var _ sdk.Msg = &MsgAddMargin{}
var _ sdk.Msg = &MsgLiquidate{}
var _ sdk.Msg = &MsgOpenPosition{}

// MsgRemoveMargin
Expand Down Expand Up @@ -46,6 +47,8 @@ func (m MsgAddMargin) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{m.Sender}
}

// MsgOpenPosition

func (m *MsgOpenPosition) ValidateBasic() error {
if m.Side != Side_SELL && m.Side != Side_BUY {
return fmt.Errorf("invalid side")
Expand All @@ -72,3 +75,29 @@ func (m *MsgOpenPosition) ValidateBasic() error {
func (m *MsgOpenPosition) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{m.Sender}
}

// MsgLiquidate

func (m MsgLiquidate) Route() string { return RouterKey }
func (m MsgLiquidate) Type() string { return "liquidate_msg" }

func (m MsgLiquidate) ValidateBasic() error {
if err := sdk.VerifyAddressFormat(m.Sender); err != nil {
return err
}
if err := sdk.VerifyAddressFormat(m.Trader); err != nil {
return err
}
if _, err := common.NewTokenPairFromStr(m.TokenPair); err != nil {
return err
}
return nil
}

func (m MsgLiquidate) GetSignBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m))
}

func (m MsgLiquidate) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{m.Sender}
}
Loading

0 comments on commit 47e1ed8

Please sign in to comment.