Skip to content

Commit

Permalink
Merge pull request #628 from UnUniFi/feature/withdraw-margin
Browse files Browse the repository at this point in the history
Impl WithdrawMargin message
  • Loading branch information
Senna46 authored Jun 25, 2023
2 parents 89155cd + 916ae55 commit 23c9281
Show file tree
Hide file tree
Showing 8 changed files with 587 additions and 52 deletions.
9 changes: 9 additions & 0 deletions proto/ununifi/derivatives/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ service Msg {
rpc ReportLiquidation(MsgReportLiquidation) returns (MsgReportLiquidationResponse);
rpc ReportLevyPeriod(MsgReportLevyPeriod) returns (MsgReportLevyPeriodResponse);
rpc AddMargin(MsgAddMargin) returns (MsgAddMarginResponse);
rpc WithdrawMargin(MsgWithdrawMargin) returns (MsgWithdrawMarginResponse);
}

message MsgDepositToPool {
Expand Down Expand Up @@ -79,3 +80,11 @@ message MsgAddMargin {
}

message MsgAddMarginResponse {}

message MsgWithdrawMargin {
string sender = 1 [(gogoproto.moretags) = "yaml:\"sender\""];
cosmos.base.v1beta1.Coin amount = 2 [(gogoproto.moretags) = "yaml:\"amount\"", (gogoproto.nullable) = false];
string position_id = 3 [(gogoproto.moretags) = "yaml:\"position_id\""];
}

message MsgWithdrawMarginResponse {}
6 changes: 6 additions & 0 deletions scripts/commands/derivatives/msgs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ $BINARY tx derivatives withdraw-from-pool 1 ubtc --from=$USER1 $conf | jq .raw_l
$BINARY tx derivatives open-position perpetual-futures 100ubtc ubtc uusdc long --from=$USER1 $conf | jq .raw_log | sed 's/\\n/\n/g'
$BINARY tx derivatives open-position perpetual-futures 100uusdc ubtc uusdc short --from=$USER1 $conf | jq .raw_log | sed 's/\\n/\n/g'

# add-margin
$BINARY tx derivatives add-margin 1 100ubtc --from=$USER1 $conf | jq .raw_log | sed 's/\\n/\n/g'

# withdraw-margin
$BINARY tx derivatives withdraw-margin 1 100ubtc --from=$USER1 $conf | jq .raw_log | sed 's/\\n/\n/g'

# query positions
$BINARY q derivatives positions $USER_ADDRESS_1

Expand Down
44 changes: 44 additions & 0 deletions x/derivatives/keeper/margin.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,50 @@ func (k Keeper) AddMargin(ctx sdk.Context, sender sdk.AccAddress, positionId str
return nil
}

func (k Keeper) WithdrawMargin(ctx sdk.Context, withdrawer sdk.AccAddress, positionId string, amount sdk.Coin) error {
position := k.GetPositionWithId(ctx, positionId)
if position == nil {
return types.ErrPositionDoesNotExist
}

// Check withdrawer (sender) matches the owner of the position
if position.Address != withdrawer.String() {
return types.ErrUnauthorized
}

// Update RemainingMargin
position.RemainingMargin = position.RemainingMargin.Sub(amount)

// Check if the updated margin is positive
if position.RemainingMargin.IsNegative() {
return types.ErrNegativeMargin
}

// Check if the updated margin is not under liquidation
params := k.GetParams(ctx)
currentBaseUsdRate, currentQuoteUsdRate, err := k.GetPairUsdPriceFromMarket(ctx, position.Market)
if err != nil {
return err
}
quoteTicker := k.GetPoolQuoteTicker(ctx)
baseMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.BaseDenom, currentBaseUsdRate)
quoteMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.QuoteDenom, currentQuoteUsdRate)

if position.NeedLiquidation(params.PerpetualFutures.MarginMaintenanceRate, baseMetricsRate, quoteMetricsRate) {
// Return err as the result of this tx
return types.ErrTooMuchMarginToWithdraw
}

// Send margin from the margin manager module account to the withdrawer
if err := k.SendBackMargin(ctx, withdrawer, sdk.NewCoins(amount)); err != nil {
return err
}

k.SetPosition(ctx, *position)

return nil
}

func (k Keeper) SendMarginToMarginManager(ctx sdk.Context, sender sdk.AccAddress, margin sdk.Coins) error {
return k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.MarginManager, margin)
}
Expand Down
6 changes: 6 additions & 0 deletions x/derivatives/keeper/margin_test.go
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
package keeper_test

// TODO: Impl unit test
// func (suite *KeeperTestSuite) TestAddMargin() {}

// TODO: Impl unit test
// func (suite *KeeperTestSuite) TestWithdrawMargin() {}
11 changes: 11 additions & 0 deletions x/derivatives/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,14 @@ func (k msgServer) AddMargin(c context.Context, msg *types.MsgAddMargin) (*types

return &types.MsgAddMarginResponse{}, nil
}

func (k msgServer) WithdrawMargin(c context.Context, msg *types.MsgWithdrawMargin) (*types.MsgWithdrawMarginResponse, error) {
ctx := sdk.UnwrapSDKContext(c)

err := k.Keeper.WithdrawMargin(ctx, sdk.AccAddress(msg.Sender), msg.PositionId, msg.Amount)
if err != nil {
return nil, err
}

return &types.MsgWithdrawMarginResponse{}, nil
}
2 changes: 2 additions & 0 deletions x/derivatives/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ var (
ErrPositionDoesNotExist = sdkerrors.Register(ModuleName, 15, "position does not exist")
ErrLiquidationNotNeeded = sdkerrors.Register(ModuleName, 16, "liquidation is not needed")
ErrLiquidationNeeded = sdkerrors.Register(ModuleName, 17, "liquidation is needed")
ErrUnauthorized = sdkerrors.Register(ModuleName, 18, "unauthorized")
ErrTooMuchMarginToWithdraw = sdkerrors.Register(ModuleName, 19, "too much margin to withdraw")
)
26 changes: 26 additions & 0 deletions x/derivatives/types/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,29 @@ func (msg MsgAddMargin) GetSigners() []sdk.AccAddress {
addr, _ := sdk.AccAddressFromBech32(msg.Sender)
return []sdk.AccAddress{addr}
}

var _ sdk.Msg = &MsgWithdrawMargin{}

func NewMsgWithdrawMargin(sender string, positionId string, amount sdk.Coin) MsgWithdrawMargin {
return MsgWithdrawMargin{
Sender: sender,
PositionId: positionId,
Amount: amount,
}
}

// ValidateBasic does a simple validation check that doesn't require access to state.
func (msg MsgWithdrawMargin) ValidateBasic() error {
_, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address is not valid")
}

return nil
}

// GetSigners returns the addresses of signers that must sign.
func (msg MsgWithdrawMargin) GetSigners() []sdk.AccAddress {
addr, _ := sdk.AccAddressFromBech32(msg.Sender)
return []sdk.AccAddress{addr}
}
Loading

0 comments on commit 23c9281

Please sign in to comment.