Skip to content

Commit

Permalink
cli test (perp): trader calls close position with an empty position (#…
Browse files Browse the repository at this point in the history
…513)

* add a new pair

* MsgClosePosition

* setup

* setup

* fix

* fix

* move user to setup

* TestZCloseEmptyPosition

* Revert "move user to setup"

This reverts commit d49fb09.

* next: finish up close empty test

* gotten to eof

* fixed but still need to return the err back and propogate it to the tx broadcast

* clean up
  • Loading branch information
kai0x authored Jun 1, 2022
1 parent 706bb01 commit 4f0beba
Show file tree
Hide file tree
Showing 12 changed files with 717 additions and 62 deletions.
16 changes: 15 additions & 1 deletion proto/perp/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ service Msg {
option (google.api.http).post = "/nibiru/perp/open_position";
}

rpc ClosePosition(MsgClosePosition) returns (MsgClosePositionResponse) {
option (google.api.http).post = "/nibiru/perp/close_position";
}

}

// -------------------------- RemoveMargin --------------------------
Expand Down Expand Up @@ -110,4 +114,14 @@ message MsgOpenPositionResponse {
}

// -------------------------- ClosePosition --------------------------
// TODO

message MsgClosePosition {
bytes sender = 1 [
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];

string token_pair = 2;
}

message MsgClosePositionResponse {

}
78 changes: 72 additions & 6 deletions x/perp/client/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type IntegrationTestSuite struct {

cfg testutilcli.Config
network *testutilcli.Network
users []sdk.AccAddress
}

func (s *IntegrationTestSuite) SetupSuite() {
Expand Down Expand Up @@ -54,6 +55,14 @@ func (s *IntegrationTestSuite) SetupSuite() {
FluctuationLimitRatio: sdk.MustNewDecFromStr("0.2"),
MaxOracleSpreadRatio: sdk.MustNewDecFromStr("0.2"),
},
{
Pair: "eth:unibi",
BaseAssetReserve: sdk.MustNewDecFromStr("10000000"),
QuoteAssetReserve: sdk.MustNewDecFromStr("60000000000"),
TradeLimitRatio: sdk.MustNewDecFromStr("0.8"),
FluctuationLimitRatio: sdk.MustNewDecFromStr("0.2"),
MaxOracleSpreadRatio: sdk.MustNewDecFromStr("0.2"),
},
}
genesisState[vpooltypes.ModuleName] = s.cfg.Codec.MustMarshalJSON(vpoolGenesis)

Expand All @@ -65,6 +74,12 @@ func (s *IntegrationTestSuite) SetupSuite() {
sdk.ZeroDec(),
},
},
{
Pair: "eth:unibi",
CumulativePremiumFractions: []sdk.Dec{
sdk.ZeroDec(),
},
},
}

genesisState[perptypes.ModuleName] = s.cfg.Codec.MustMarshalJSON(perpGenesis)
Expand All @@ -76,6 +91,20 @@ func (s *IntegrationTestSuite) SetupSuite() {

_, err := s.network.WaitForHeight(1)
s.Require().NoError(err)

val := s.network.Validators[0]
info, _, err := val.ClientCtx.Keyring.
NewMnemonic("user1", keyring.English, sdk.FullFundraiserPath, "", hd.Secp256k1)
s.Require().NoError(err)
user1 := sdk.AccAddress(info.GetPubKey().Address())

info2, _, err := val.ClientCtx.Keyring.
NewMnemonic("user2", keyring.English, sdk.FullFundraiserPath, "", hd.Secp256k1)
s.Require().NoError(err)
user2 := sdk.AccAddress(info2.GetPubKey().Address())

// TODO: figure out why using user2 gives a "key <addr> not found" error
s.users = []sdk.AccAddress{user1, user2}
}

func (s *IntegrationTestSuite) TearDownSuite() {
Expand All @@ -90,13 +119,9 @@ func (s *IntegrationTestSuite) TestOpenPositionCmd() {
Token1: "unibi",
}

info, _, err := val.ClientCtx.Keyring.
NewMnemonic("user1", keyring.English, sdk.FullFundraiserPath, "", hd.Secp256k1)
s.Require().NoError(err)

user := sdk.AccAddress(info.GetPubKey().Address())
user := s.users[0]

_, err = utils.FillWalletFromValidator(user,
_, err := utils.FillWalletFromValidator(user,
sdk.NewCoins(
sdk.NewInt64Coin(s.cfg.BondDenom, 20_000),
sdk.NewInt64Coin(common.GovDenom, 100_000_000),
Expand Down Expand Up @@ -184,6 +209,47 @@ func (s *IntegrationTestSuite) TestOpenPositionCmd() {
s.Require().Equal(sdk.MustNewDecFromStr("999900"), queryResp.Position.OpenNotional)
}

func (s *IntegrationTestSuite) TestPositionEmptyAndClose() {
val := s.network.Validators[0]
pair := common.AssetPair{
Token0: "eth",
Token1: "unibi",
}

user := s.users[0]

_, err := utils.FillWalletFromValidator(user,
sdk.NewCoins(
sdk.NewInt64Coin(s.cfg.BondDenom, 20_000),
sdk.NewInt64Coin(common.GovDenom, 100_000_000),
sdk.NewInt64Coin(common.CollDenom, 100_000_000),
),
val,
s.cfg.BondDenom,
)
s.Require().NoError(err)

// verify trader has no position (empty)
_, err = testutilcli.QueryTraderPosition(val.ClientCtx, pair, user)
s.Require().True(strings.Contains(err.Error(), "no position found"))

// close position should produce error
args := []string{
"--from",
user.String(),
fmt.Sprintf("%s%s%s", "eth", common.PairSeparator, "unibi"),
}
commonArgs := []string{
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()),
}
// TODO: fix that this err doesn't get propagated back up to show up here
res, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cli.ClosePositionCmd(), append(args, commonArgs...))
s.T().Logf("res: %+v", res)
s.T().Logf("err: %+v", err)
}

func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}
36 changes: 36 additions & 0 deletions x/perp/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,42 @@ func OpenPositionCmd() *cobra.Command {
return cmd
}

// TODO: how is a position idenitfiied? by pair? by id?
func ClosePositionCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "close-position [pair]",
Short: "Closes a position",
Args: cobra.ExactArgs(1),
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)

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

err = tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg)
if err != nil {
return err
}
return nil
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}

/*
RemoveMarginCmd is a CLI command that removes margin from a position,
realizing any outstanding funding payments and decreasing the margin ratio.
Expand Down
3 changes: 3 additions & 0 deletions x/perp/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
case *types.MsgLiquidate:
res, err := msgServer.Liquidate(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgClosePosition:
res, err := msgServer.ClosePosition(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
default:
errMsg := fmt.Sprintf(
"unrecognized %s message type: %T", types.ModuleName, msg)
Expand Down
29 changes: 29 additions & 0 deletions x/perp/keeper/clearing_house.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,35 @@ func (k Keeper) OpenPosition(
})
}

func (k Keeper) ClosePosition(
ctx sdk.Context,
pair common.AssetPair,
traderAddr sdk.AccAddress,
) (err error) {
// checks
err = k.requireVpool(ctx, pair)
if err != nil {
return err
}

position, err := k.GetPosition(ctx, pair, traderAddr)
if err != nil {
// TODO: propagate this back to the cli
return err
}

_, err = k.closePositionEntirely(
ctx,
*position,
sdk.ZeroDec(), // TODO: double check this
)

if err != nil {
return err
}
return nil
}

/*
increases a position by increasedNotional amount in margin units.
Calculates the amount of margin required given the leverage parameter.
Expand Down
18 changes: 18 additions & 0 deletions x/perp/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,21 @@ func (k msgServer) Liquidate(goCtx context.Context, msg *types.MsgLiquidate,

return response, nil
}

func (k msgServer) ClosePosition(goCtx context.Context, req *types.MsgClosePosition,
) (*types.MsgClosePositionResponse, error) {
pair, err := common.NewAssetPairFromStr(req.TokenPair)
if err != nil {
panic(err) // must not happen
}

ctx := sdk.UnwrapSDKContext(goCtx)

// TODO: fix that this err doesn't get returned if using tx broadcast in cli_test
err = k.k.ClosePosition(ctx, pair, req.Sender)
if err != nil {
return nil, sdkerrors.Wrap(vpooltypes.ErrClosingPosition, err.Error())
}

return &types.MsgClosePositionResponse{}, nil
}
2 changes: 2 additions & 0 deletions x/perp/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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)
cdc.RegisterConcrete(&MsgClosePosition{}, "perp/close_position", nil)
}

func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
Expand All @@ -21,6 +22,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
&MsgAddMargin{},
&MsgLiquidate{},
&MsgOpenPosition{},
&MsgClosePosition{},
)

msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
Expand Down
26 changes: 26 additions & 0 deletions x/perp/types/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,29 @@ func (m MsgLiquidate) GetSignBytes() []byte {
func (m MsgLiquidate) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{m.Sender}
}

// MsgClosePosition

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

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

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

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

0 comments on commit 4f0beba

Please sign in to comment.