Skip to content

Commit

Permalink
Add Checks in UpdateTakeProfitPrice and UpdateStopLossPrice keeper fu…
Browse files Browse the repository at this point in the history
…nctions (#973)

* add checks in update take profit and stop loss price

* refactor update take profit price test into table format

* add tests cases for update take profit

* refactor and add test cases in update stop loss

---------

Co-authored-by: Cosmic Vagabond <121588426+cosmic-vagabond@users.noreply.github.com>
  • Loading branch information
99adarsh and cosmic-vagabond authored Nov 20, 2024
1 parent a31a894 commit 210d25d
Show file tree
Hide file tree
Showing 5 changed files with 469 additions and 171 deletions.
4 changes: 3 additions & 1 deletion x/perpetual/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const (
initChain = true
)

var oracleProvider = sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address())

var (
priceMap = map[string]assetPriceInfo{
"uusdc": {
Expand Down Expand Up @@ -111,7 +113,7 @@ func (suite *PerpetualKeeperTestSuite) AddBlockTime(d time.Duration) {

func (suite *PerpetualKeeperTestSuite) SetupCoinPrices() {
// prices set for USDT and USDC
provider := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address())
provider := oracleProvider

for _, v := range priceMap {
suite.app.OracleKeeper.SetAssetInfo(suite.ctx, oracletypes.AssetInfo{
Expand Down
16 changes: 16 additions & 0 deletions x/perpetual/keeper/msg_server_update_stop_loss.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@ func (k msgServer) UpdateStopLoss(goCtx context.Context, msg *types.MsgUpdateSto
return nil, errorsmod.Wrap(types.ErrPoolDoesNotExist, fmt.Sprintf("poolId: %d", poolId))
}

tradingAssetPrice, err := k.GetAssetPrice(ctx, mtp.TradingAsset)
if err != nil {
return nil, err
}

if mtp.Position == types.Position_LONG {
if !msg.Price.IsZero() && msg.Price.GTE(tradingAssetPrice) {
return nil, fmt.Errorf("stop loss price cannot be greater than equal to tradingAssetPrice for long (Stop loss: %s, asset price: %s)", msg.Price.String(), tradingAssetPrice.String())
}
}
if mtp.Position == types.Position_SHORT {
if !msg.Price.IsZero() && msg.Price.LTE(tradingAssetPrice) {
return nil, fmt.Errorf("stop loss price cannot be less than equal to tradingAssetPrice for short (Stop loss: %s, asset price: %s)", msg.Price.String(), tradingAssetPrice.String())
}
}

mtp.StopLossPrice = msg.Price
err = k.SetMTP(ctx, &mtp)
if err != nil {
Expand Down
259 changes: 193 additions & 66 deletions x/perpetual/keeper/msg_server_update_stop_loss_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,86 +4,213 @@ import (
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/elys-network/elys/testutil/sample"
oracletypes "github.com/elys-network/elys/x/oracle/types"
ptypes "github.com/elys-network/elys/x/parameter/types"
"github.com/elys-network/elys/x/perpetual/keeper"
"github.com/elys-network/elys/x/perpetual/types"
)

func (suite *PerpetualKeeperTestSuite) TestMsgServerUpdateStopLoss_ErrorGetMtp() {
k := suite.app.PerpetualKeeper
ctx := suite.ctx
msg := keeper.NewMsgServerImpl(*k)
_, err := msg.UpdateStopLoss(ctx, &types.MsgUpdateStopLoss{
Creator: sample.AccAddress(),
Id: 1,
Price: math.LegacyMustNewDecFromStr("0.98"),
})
suite.Require().ErrorIs(err, types.ErrMTPDoesNotExist)
}
func (suite *PerpetualKeeperTestSuite) TestUpdateStopLossPrice() {

func (suite *PerpetualKeeperTestSuite) TestMsgServerUpdateStopLoss_ErrPoolDoesNotExist() {
k := suite.app.PerpetualKeeper
ctx := suite.ctx
msg := keeper.NewMsgServerImpl(*k)
// Define test cases
testCases := []struct {
name string
setup func() *types.MsgUpdateStopLoss
expectedErrMsg string
}{
{
"mtp not found",
func() *types.MsgUpdateStopLoss {
return &types.MsgUpdateStopLoss{
Creator: sample.AccAddress(),
Id: uint64(10),
Price: math.LegacyNewDec(2),
}
},
"mtp not found",
},
{
"perpetual pool does not exist",
func() *types.MsgUpdateStopLoss {
addr := suite.AddAccounts(1, nil)
positionCreator := addr[0]
_, _, ammPool := suite.SetPerpetualPool(1)
tradingAssetPrice, err := suite.app.PerpetualKeeper.GetAssetPrice(suite.ctx, ptypes.ATOM)
suite.Require().NoError(err)
openPositionMsg := &types.MsgOpen{
Creator: positionCreator.String(),
Leverage: math.LegacyNewDec(2),
Position: types.Position_LONG,
PoolId: ammPool.PoolId,
TradingAsset: ptypes.ATOM,
Collateral: sdk.NewCoin(ptypes.BaseCurrency, math.NewInt(1000)),
TakeProfitPrice: tradingAssetPrice.MulInt64(4),
StopLossPrice: math.LegacyZeroDec(),
}

firstPool := uint64(1)
suite.SetPerpetualPool(firstPool)
position, err := suite.app.PerpetualKeeper.Open(suite.ctx, openPositionMsg)
suite.Require().NoError(err)
suite.app.PerpetualKeeper.RemovePool(suite.ctx, ammPool.PoolId)
return &types.MsgUpdateStopLoss{
Creator: positionCreator.String(),
Id: position.Id,
Price: math.LegacyNewDec(2),
}
},
"perpetual pool does not exist",
},
{
"asset profile not found",
func() *types.MsgUpdateStopLoss {
suite.ResetSuite()
addr := suite.AddAccounts(1, nil)
positionCreator := addr[0]
_, _, ammPool := suite.SetPerpetualPool(1)
tradingAssetPrice, err := suite.app.PerpetualKeeper.GetAssetPrice(suite.ctx, ptypes.ATOM)
suite.Require().NoError(err)
openPositionMsg := &types.MsgOpen{
Creator: positionCreator.String(),
Leverage: math.LegacyNewDec(2),
Position: types.Position_LONG,
PoolId: ammPool.PoolId,
TradingAsset: ptypes.ATOM,
Collateral: sdk.NewCoin(ptypes.BaseCurrency, math.NewInt(1000)),
TakeProfitPrice: tradingAssetPrice.MulInt64(4),
StopLossPrice: math.LegacyZeroDec(),
}

amount := math.NewInt(400)
addr := suite.AddAccounts(2, nil)
firstPositionCreator := addr[0]
position, err := suite.app.PerpetualKeeper.Open(suite.ctx, openPositionMsg)
suite.Require().NoError(err)
suite.app.OracleKeeper.RemoveAssetInfo(suite.ctx, ptypes.ATOM)
return &types.MsgUpdateStopLoss{
Creator: positionCreator.String(),
Id: position.Id,
Price: math.LegacyNewDec(2),
}
},
"asset price uatom not found",
},
{
"success: Stop Loss price updated",
func() *types.MsgUpdateStopLoss {
suite.ResetSuite()
addr := suite.AddAccounts(1, nil)
positionCreator := addr[0]
_, _, ammPool := suite.SetPerpetualPool(1)
tradingAssetPrice, err := suite.app.PerpetualKeeper.GetAssetPrice(suite.ctx, ptypes.ATOM)
suite.Require().NoError(err)
openPositionMsg := &types.MsgOpen{
Creator: positionCreator.String(),
Leverage: math.LegacyNewDec(2),
Position: types.Position_LONG,
PoolId: ammPool.PoolId,
TradingAsset: ptypes.ATOM,
Collateral: sdk.NewCoin(ptypes.BaseCurrency, math.NewInt(1000)),
TakeProfitPrice: tradingAssetPrice.MulInt64(4),
StopLossPrice: math.LegacyZeroDec(),
}

firstOpenPositionMsg := &types.MsgOpen{
Creator: firstPositionCreator.String(),
Leverage: math.LegacyNewDec(5),
Position: types.Position_SHORT,
PoolId: firstPool,
TradingAsset: ptypes.ATOM,
Collateral: sdk.NewCoin(ptypes.BaseCurrency, amount),
TakeProfitPrice: math.LegacyMustNewDecFromStr("0.95"),
StopLossPrice: math.LegacyZeroDec(),
}
position, err := suite.app.PerpetualKeeper.Open(suite.ctx, openPositionMsg)
suite.Require().NoError(err)

firstPosition, err := suite.app.PerpetualKeeper.Open(ctx, firstOpenPositionMsg)
suite.Require().NoError(err)
k.RemovePool(ctx, firstPool)
_, err = msg.UpdateStopLoss(ctx, &types.MsgUpdateStopLoss{
Creator: firstPositionCreator.String(),
Id: firstPosition.Id,
Price: math.LegacyMustNewDecFromStr("0.98"),
})
suite.Require().ErrorIs(err, types.ErrPoolDoesNotExist)
}
return &types.MsgUpdateStopLoss{
Creator: positionCreator.String(),
Id: position.Id,
Price: math.LegacyNewDec(4),
}
},
"",
},
{
"updated Stop Loss price is greater than current market price for long",
func() *types.MsgUpdateStopLoss {
suite.ResetSuite()
addr := suite.AddAccounts(1, nil)
positionCreator := addr[0]
_, _, ammPool := suite.SetPerpetualPool(1)
tradingAssetPrice, err := suite.app.PerpetualKeeper.GetAssetPrice(suite.ctx, ptypes.ATOM)
suite.Require().NoError(err)
openPositionMsg := &types.MsgOpen{
Creator: positionCreator.String(),
Leverage: math.LegacyNewDec(2),
Position: types.Position_LONG,
PoolId: ammPool.PoolId,
TradingAsset: ptypes.ATOM,
Collateral: sdk.NewCoin(ptypes.BaseCurrency, math.NewInt(1000)),
TakeProfitPrice: tradingAssetPrice.MulInt64(4),
StopLossPrice: math.LegacyZeroDec(),
}

position, err := suite.app.PerpetualKeeper.Open(suite.ctx, openPositionMsg)
suite.Require().NoError(err)

func (suite *PerpetualKeeperTestSuite) TestMsgServerUpdateStopLoss_Successful() {
k := suite.app.PerpetualKeeper
ctx := suite.ctx
msg := keeper.NewMsgServerImpl(*k)
suite.app.OracleKeeper.SetPrice(suite.ctx, oracletypes.Price{
Asset: "ATOM",
Price: math.LegacyNewDec(25),
Source: "elys",
Provider: oracleProvider.String(),
Timestamp: uint64(suite.ctx.BlockTime().Unix()),
})

firstPool := uint64(1)
suite.SetPerpetualPool(firstPool)
return &types.MsgUpdateStopLoss{
Creator: positionCreator.String(),
Id: position.Id,
Price: math.LegacyNewDec(30),
}
},
"stop loss price cannot be greater than equal to tradingAssetPrice for long",
},
{
"updated Stop Loss price is less than current market price for short",
func() *types.MsgUpdateStopLoss {
suite.ResetSuite()

amount := math.NewInt(400)
addr := suite.AddAccounts(2, nil)
firstPositionCreator := addr[0]
addr := suite.AddAccounts(1, nil)
positionCreator := addr[0]
_, _, ammPool := suite.SetPerpetualPool(1)
openPositionMsg := &types.MsgOpen{
Creator: positionCreator.String(),
Leverage: math.LegacyNewDec(5),
Position: types.Position_SHORT,
PoolId: ammPool.PoolId,
TradingAsset: ptypes.ATOM,
Collateral: sdk.NewCoin(ptypes.BaseCurrency, math.NewInt(1000)),
TakeProfitPrice: math.LegacyMustNewDecFromStr("2.9"),
StopLossPrice: math.LegacyZeroDec(),
}
position, err := suite.app.PerpetualKeeper.Open(suite.ctx, openPositionMsg)
suite.Require().NoError(err)

firstOpenPositionMsg := &types.MsgOpen{
Creator: firstPositionCreator.String(),
Leverage: math.LegacyNewDec(5),
Position: types.Position_SHORT,
PoolId: firstPool,
TradingAsset: ptypes.ATOM,
Collateral: sdk.NewCoin(ptypes.BaseCurrency, amount),
TakeProfitPrice: math.LegacyMustNewDecFromStr("0.95"),
StopLossPrice: math.LegacyZeroDec(),
suite.app.OracleKeeper.SetPrice(suite.ctx, oracletypes.Price{
Asset: "ATOM",
Price: math.LegacyNewDec(3),
Source: "elys",
Provider: oracleProvider.String(),
Timestamp: uint64(suite.ctx.BlockTime().Unix()),
})

return &types.MsgUpdateStopLoss{
Creator: positionCreator.String(),
Id: position.Id,
Price: math.LegacyMustNewDecFromStr("2"),
}
},
"stop loss price cannot be less than equal to tradingAssetPrice for short",
},
}

firstPosition, err := suite.app.PerpetualKeeper.Open(ctx, firstOpenPositionMsg)
suite.Require().NoError(err)
_, err = msg.UpdateStopLoss(ctx, &types.MsgUpdateStopLoss{
Creator: firstPositionCreator.String(),
Id: firstPosition.Id,
Price: math.LegacyMustNewDecFromStr("0.98"),
})
suite.Require().Nil(err)
for _, tc := range testCases {
suite.Run(tc.name, func() {
msg := tc.setup()
msgSrvr := keeper.NewMsgServerImpl(*suite.app.PerpetualKeeper)
_, err := msgSrvr.UpdateStopLoss(suite.ctx, msg)

if tc.expectedErrMsg != "" {
suite.Require().Error(err)
suite.Require().Contains(err.Error(), tc.expectedErrMsg)
} else {
suite.Require().NoError(err)
}
})
}
}
19 changes: 19 additions & 0 deletions x/perpetual/keeper/msg_server_update_take_profit_price.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@ func (k msgServer) UpdateTakeProfitPrice(goCtx context.Context, msg *types.MsgUp
return nil, errorsmod.Wrap(types.ErrPoolDoesNotExist, fmt.Sprintf("poolId: %d", poolId))
}

params := k.GetParams(ctx)

tradingAssetPrice, err := k.GetAssetPrice(ctx, mtp.TradingAsset)
if err != nil {
return nil, err
}

ratio := msg.Price.Quo(tradingAssetPrice)
if mtp.Position == types.Position_LONG {
if ratio.LT(params.MinimumLongTakeProfitPriceRatio) || ratio.GT(params.MaximumLongTakeProfitPriceRatio) {
return nil, fmt.Errorf("take profit price should be between %s and %s times of current market price for long (current ratio: %s)", params.MinimumLongTakeProfitPriceRatio.String(), params.MaximumLongTakeProfitPriceRatio.String(), ratio.String())
}
}
if mtp.Position == types.Position_SHORT {
if ratio.GT(params.MaximumShortTakeProfitPriceRatio) {
return nil, fmt.Errorf("take profit price should be less than %s times of current market price for short (current ratio: %s)", params.MaximumShortTakeProfitPriceRatio.String(), ratio.String())
}
}

mtp.TakeProfitPrice = msg.Price
mtp.TakeProfitCustody = types.CalcMTPTakeProfitCustody(mtp)
mtp.TakeProfitLiabilities, err = k.CalcMTPTakeProfitLiability(ctx, mtp)
Expand Down
Loading

0 comments on commit 210d25d

Please sign in to comment.