Skip to content

Commit

Permalink
Merge pull request #472 from neutron-org/feat/add_price_to_lo
Browse files Browse the repository at this point in the history
Feat/add price to limit order
  • Loading branch information
pr0n00gler authored May 31, 2024
2 parents 1d8f68e + 0d92a8d commit 4609520
Show file tree
Hide file tree
Showing 16 changed files with 630 additions and 138 deletions.
10 changes: 9 additions & 1 deletion proto/neutron/dex/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ message MsgPlaceLimitOrder {
string receiver = 2;
string token_in = 3;
string token_out = 4;
int64 tick_index_in_to_out = 5;

// DEPRECATED: tick_index_in_to_out will be removed in future release; limit_sell_price should be used instead.
int64 tick_index_in_to_out = 5 [deprecated = true];
string amount_in = 7 [
(gogoproto.moretags) = "yaml:\"amount_in\"",
(gogoproto.customtype) = "cosmossdk.io/math.Int",
Expand All @@ -132,6 +134,12 @@ message MsgPlaceLimitOrder {
(gogoproto.nullable) = true,
(gogoproto.jsontag) = "max_amount_out"
];
string limit_sell_price = 11 [
(gogoproto.moretags) = "yaml:\"limit_sell_price\"",
(gogoproto.customtype) = "github.com/neutron-org/neutron/v4/utils/math.PrecDec",
(gogoproto.nullable) = true,
(gogoproto.jsontag) = "limit_sell_price"
];
}

message MsgPlaceLimitOrderResponse {
Expand Down
6 changes: 3 additions & 3 deletions utils/math/prec_dec.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

// NOTE: This file is nearly direct copy from cosmossdk.io/math/dec.go @v1.01
// The Precesion has been changed from 18 to 26
// The Precesion has been changed from 18 to 27

// NOTE: never use new(Dec) or else we will panic unmarshalling into the
// nil embedded big.Int
Expand All @@ -23,11 +23,11 @@ type PrecDec struct {

const (
// number of decimal places
Precision = 26
Precision = 27

// bits required to represent the above precision
// Ceiling[Log2[10^Precision - 1]]
PrecDecimalPrecisionBits = 87
PrecDecimalPrecisionBits = 90

// decimalTruncateBits is the minimum number of bits removed
// by a truncate operation. It is equal to
Expand Down
11 changes: 7 additions & 4 deletions wasmbinding/bindings/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,17 @@ type Dex struct {
// MsgPlaceLimitOrder is a copy dextypes.MsgPlaceLimitOrder with altered ExpirationTime field,
// it's a preferable way to pass timestamp as unixtime to contracts
type MsgPlaceLimitOrder struct {
Creator string `json:"creator,omitempty"`
Receiver string `json:"receiver,omitempty"`
TokenIn string `json:"token_in,omitempty"`
TokenOut string `json:"token_out,omitempty"`
Creator string `json:"creator,omitempty"`
Receiver string `json:"receiver,omitempty"`
TokenIn string `json:"token_in,omitempty"`
TokenOut string `json:"token_out,omitempty"`
// Deprecated: tick_index_in_to_out will be removed in future release; limit_sell_price should be used instead.
TickIndexInToOut int64 `json:"tick_index_in_to_out,omitempty"`
AmountIn math.Int `json:"amount_in"`
OrderType string `json:"order_type,omitempty"`
// expirationTime is only valid iff orderType == GOOD_TIL_TIME.
ExpirationTime *uint64 `json:"expiration_time,omitempty"`
MaxAmountOut *math.Int `json:"max_amount_out"`
// Accepts standard decimals and decimals with scientific notation (ie. 1234.23E-7)
LimitSellPrice string `json:"limit_sell_price,omitempty"`
}
19 changes: 15 additions & 4 deletions wasmbinding/message_plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

dexkeeper "github.com/neutron-org/neutron/v4/x/dex/keeper"
dextypes "github.com/neutron-org/neutron/v4/x/dex/types"
dexutils "github.com/neutron-org/neutron/v4/x/dex/utils"

contractmanagerkeeper "github.com/neutron-org/neutron/v4/x/contractmanager/keeper"

Expand Down Expand Up @@ -223,10 +224,11 @@ func (m *CustomMessenger) dispatchDexMsg(ctx sdk.Context, contractAddr sdk.AccAd
return handleDexMsg(ctx, dex.Withdrawal, m.DexMsgServer.Withdrawal)
case dex.PlaceLimitOrder != nil:
msg := dextypes.MsgPlaceLimitOrder{
Creator: contractAddr.String(),
Receiver: dex.PlaceLimitOrder.Receiver,
TokenIn: dex.PlaceLimitOrder.TokenIn,
TokenOut: dex.PlaceLimitOrder.TokenOut,
Creator: contractAddr.String(),
Receiver: dex.PlaceLimitOrder.Receiver,
TokenIn: dex.PlaceLimitOrder.TokenIn,
TokenOut: dex.PlaceLimitOrder.TokenOut,
//nolint: staticcheck // TODO: remove in next release
TickIndexInToOut: dex.PlaceLimitOrder.TickIndexInToOut,
AmountIn: dex.PlaceLimitOrder.AmountIn,
MaxAmountOut: dex.PlaceLimitOrder.MaxAmountOut,
Expand All @@ -246,6 +248,15 @@ func (m *CustomMessenger) dispatchDexMsg(ctx sdk.Context, contractAddr sdk.AccAd
t := time.Unix(int64(*(dex.PlaceLimitOrder.ExpirationTime)), 0)
msg.ExpirationTime = &t
}

if limitPriceStr := dex.PlaceLimitOrder.LimitSellPrice; limitPriceStr != "" {
limitPriceDec, err := dexutils.ParsePrecDecScientificNotation(limitPriceStr)
if err != nil {
return nil, nil, errors.Wrapf(err, "cannot parse string %s for limit price", limitPriceStr)
}
msg.LimitSellPrice = &limitPriceDec
}

return handleDexMsg(ctx, &msg, m.DexMsgServer.PlaceLimitOrder)
case dex.CancelLimitOrder != nil:
dex.CancelLimitOrder.Creator = contractAddr.String()
Expand Down
7 changes: 7 additions & 0 deletions x/dex/client/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const (
FlagMaxAmountOut = "max-amount-out"
FlagIncludePoolData = "include-pool-data"
FlagCalcWithdraw = "calc-withdraw"
FlagPrice = "price"
)

func FlagSetMaxAmountOut() *flag.FlagSet {
Expand All @@ -14,6 +15,12 @@ func FlagSetMaxAmountOut() *flag.FlagSet {
return fs
}

func FlagSetPrice() *flag.FlagSet {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.String(FlagPrice, "", "Sell price for limit order")
return fs
}

func FlagSetIncludePoolData() *flag.FlagSet {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.Bool(FlagIncludePoolData, false, "Include pool data with response")
Expand Down
20 changes: 19 additions & 1 deletion x/dex/client/cli/tx_place_limit_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import (
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/spf13/cobra"

math_utils "github.com/neutron-org/neutron/v4/utils/math"
"github.com/neutron-org/neutron/v4/x/dex/types"
)

func CmdPlaceLimitOrder() *cobra.Command {
cmd := &cobra.Command{
//nolint:lll
Use: "place-limit-order [receiver] [token-in] [token-out] [tick-index] [amount-in] ?[order-type] ?[expirationTime] ?(--max-amout-out)",
Use: "place-limit-order [receiver] [token-in] [token-out] [tick-index] [amount-in] ?[order-type] ?[expirationTime] ?(--max-amout-out) ?(--price)",
Short: "Broadcast message PlaceLimitOrder",
Example: "place-limit-order alice tokenA tokenB [-10] tokenA 50 GOOD_TIL_TIME '01/02/2006 15:04:05' --max-amount-out 20 --from alice",
Args: cobra.RangeArgs(5, 7),
Expand Down Expand Up @@ -65,6 +66,7 @@ func CmdPlaceLimitOrder() *cobra.Command {
if err != nil {
return err
}

var maxAmountOutIntP *math.Int
if maxAmountOutArg != "" {
maxAmountOutInt, ok := math.NewIntFromString(maxAmountOutArg)
Expand All @@ -77,6 +79,20 @@ func CmdPlaceLimitOrder() *cobra.Command {
maxAmountOutIntP = &maxAmountOutInt
}

priceArg, err := cmd.Flags().GetString(FlagPrice)
if err != nil {
return err
}

var priceDecP *math_utils.PrecDec
if priceArg != "" {
priceDec, err := math_utils.NewPrecDecFromStr(priceArg)
if err != nil {
return err
}
priceDecP = &priceDec
}

clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
Expand All @@ -92,6 +108,7 @@ func CmdPlaceLimitOrder() *cobra.Command {
orderType,
goodTil,
maxAmountOutIntP,
priceDecP,
)

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
Expand All @@ -100,6 +117,7 @@ func CmdPlaceLimitOrder() *cobra.Command {

flags.AddTxFlagsToCmd(cmd)
cmd.Flags().AddFlagSet(FlagSetMaxAmountOut())
cmd.Flags().AddFlagSet(FlagSetPrice())

return cmd
}
36 changes: 36 additions & 0 deletions x/dex/keeper/integration_placelimitorder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
sdkmath "cosmossdk.io/math"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

math_utils "github.com/neutron-org/neutron/v4/utils/math"
"github.com/neutron-org/neutron/v4/x/dex/types"
)

Expand Down Expand Up @@ -346,6 +347,41 @@ func (s *DexTestSuite) TestPlaceLimitOrderTooSmallAfterSwapFails() {
s.assertAliceLimitSellFails(types.ErrTradeTooSmall, "TokenA", 149_149, 5)
}

func (s *DexTestSuite) TestPlaceLimitOrderWithPrice0To1() {
s.fundAliceBalances(10, 0)
s.fundBobBalances(0, 100)

// GIVEN
// Alice place LO at price ~10.0
trancheKey0 := s.limitSellsWithPrice(s.alice, "TokenA", math_utils.NewPrecDec(10), 10)

// WHEN bob swaps through all of Alice's LO
s.bobLimitSells("TokenB", -23078, 100, types.LimitOrderType_IMMEDIATE_OR_CANCEL)
s.aliceWithdrawsLimitSell(trancheKey0)

// THEN alice gets out ~100 TOKENB and bob gets ~10 TOKENA
s.assertAliceBalancesInt(sdkmath.ZeroInt(), sdkmath.NewInt(99_999_977))
s.assertBobBalancesInt(sdkmath.NewInt(10000000), sdkmath.NewInt(22))
}

func (s *DexTestSuite) TestPlaceLimitOrderWithPrice1To0() {
s.fundAliceBalances(0, 200)
s.fundBobBalances(10, 0)
makerPrice := math_utils.MustNewPrecDecFromStr("0.25")
takerPrice := math_utils.MustNewPrecDecFromStr("3.99")
// GIVEN
// Alice place LO at price ~.25
trancheKey0 := s.limitSellsWithPrice(s.alice, "TokenB", makerPrice, 200)

// WHEN bob swaps through Alice's LO
s.limitSellsWithPrice(s.bob, "TokenA", takerPrice, 10)
s.aliceWithdrawsLimitSell(trancheKey0)

// THEN alice gets out ~10 TOKENA and bob gets ~40 TOKENB
s.assertAliceBalancesInt(sdkmath.NewInt(9999999), sdkmath.ZeroInt())
s.assertBobBalancesInt(sdkmath.ZeroInt(), sdkmath.NewInt(40001452))
}

// Fill Or Kill limit orders ///////////////////////////////////////////////////////////
func (s *DexTestSuite) TestPlaceLimitOrderFoKNoLiq() {
s.fundAliceBalances(10, 0)
Expand Down
9 changes: 8 additions & 1 deletion x/dex/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,19 @@ func (k MsgServer) PlaceLimitOrder(
if err != nil {
return &types.MsgPlaceLimitOrderResponse{}, err
}
tickIndex := msg.TickIndexInToOut
if msg.LimitSellPrice != nil {
tickIndex, err = types.CalcTickIndexFromPrice(*msg.LimitSellPrice)
if err != nil {
return &types.MsgPlaceLimitOrderResponse{}, errors.Wrapf(err, "invalid LimitSellPrice %s", msg.LimitSellPrice.String())
}
}
trancheKey, coinIn, _, coinOutSwap, err := k.PlaceLimitOrderCore(
goCtx,
msg.TokenIn,
msg.TokenOut,
msg.AmountIn,
msg.TickIndexInToOut,
tickIndex,
msg.OrderType,
msg.ExpirationTime,
msg.MaxAmountOut,
Expand Down
63 changes: 63 additions & 0 deletions x/dex/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,29 @@ func (s *DexTestSuite) limitSellsWithMaxOut(
return msg.TrancheKey
}

func (s *DexTestSuite) limitSellsWithPrice(
account sdk.AccAddress,
tokenIn string,
price math_utils.PrecDec,
amountIn int,
) string {
tokenIn, tokenOut := dexkeeper.GetInOutTokens(tokenIn, "TokenA", "TokenB")

msg, err := s.msgServer.PlaceLimitOrder(s.Ctx, &types.MsgPlaceLimitOrder{
Creator: account.String(),
Receiver: account.String(),
TokenIn: tokenIn,
TokenOut: tokenOut,
LimitSellPrice: &price,
AmountIn: sdkmath.NewInt(int64(amountIn)).Mul(denomMultiple),
OrderType: types.LimitOrderType_GOOD_TIL_CANCELLED,
})

s.Assert().NoError(err)

return msg.TrancheKey
}

func (s *DexTestSuite) limitSellsInt(
account sdk.AccAddress,
tokenIn string,
Expand Down Expand Up @@ -1982,6 +2005,9 @@ func TestMsgPlaceLimitOrderValidate(t *testing.T) {

ZEROINT := sdkmath.ZeroInt()
ONEINT := sdkmath.OneInt()
TINYDEC := math_utils.MustNewPrecDecFromStr("0.000000000000000000000000494")
HUGEDEC := math_utils.MustNewPrecDecFromStr("2020125331305056766452345.127500016657360222036663652")
FIVEDEC := math_utils.NewPrecDec(5)
tests := []struct {
name string
msg types.MsgPlaceLimitOrder
Expand Down Expand Up @@ -2113,6 +2139,43 @@ func TestMsgPlaceLimitOrderValidate(t *testing.T) {
},
types.ErrTickOutsideRange,
},
{
"price < minPrice",
types.MsgPlaceLimitOrder{
Creator: sample.AccAddress(),
Receiver: sample.AccAddress(),
TokenIn: "TokenA",
TokenOut: "TokenB",
LimitSellPrice: &TINYDEC,
AmountIn: sdkmath.OneInt(),
},
types.ErrPriceOutsideRange,
},
{
"price > maxPrice",
types.MsgPlaceLimitOrder{
Creator: sample.AccAddress(),
Receiver: sample.AccAddress(),
TokenIn: "TokenA",
TokenOut: "TokenB",
LimitSellPrice: &HUGEDEC,
AmountIn: sdkmath.OneInt(),
},
types.ErrPriceOutsideRange,
},
{
"invalid tickIndexInToOut & LimitSellPrice",
types.MsgPlaceLimitOrder{
Creator: sample.AccAddress(),
Receiver: sample.AccAddress(),
TokenIn: "TokenA",
TokenOut: "TokenB",
LimitSellPrice: &FIVEDEC,
TickIndexInToOut: 6,
AmountIn: sdkmath.OneInt(),
},
types.ErrInvalidPriceAndTick,
},
}

for _, tt := range tests {
Expand Down
16 changes: 16 additions & 0 deletions x/dex/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,20 @@ var (
1158,
"Cannot deposit at a price below the opposing token's current price",
)
ErrCalcTickFromPrice = sdkerrors.Register(
ModuleName,
1159,
"Cannot convert price to int64 tick value",
)
ErrPriceOutsideRange = sdkerrors.Register(
ModuleName,
1160,
"Invalid price; 0.00000000000000000000000050 < PRICE > 2020125331305056766451886.728",
)

ErrInvalidPriceAndTick = sdkerrors.Register(
ModuleName,
1161,
"Only LimitSellPrice or TickIndexInToOut should be specified",
)
)
Loading

0 comments on commit 4609520

Please sign in to comment.