diff --git a/x/dex/keeper/integration_placelimitorder_test.go b/x/dex/keeper/integration_placelimitorder_test.go index c856dabb1..91ceb2239 100644 --- a/x/dex/keeper/integration_placelimitorder_test.go +++ b/x/dex/keeper/integration_placelimitorder_test.go @@ -314,26 +314,26 @@ func (s *DexTestSuite) TestPlaceLimitOrderWithPrice0To1() { s.bobLimitSells("TokenB", -23078, 100, types.LimitOrderType_IMMEDIATE_OR_CANCEL) s.aliceWithdrawsLimitSell(trancheKey0) - // THEN alice gets out ~100 TOKENB and bob pays ~100 TOKENA + // THEN alice gets out ~100 TOKENB and bob gets ~10 TOKENA s.assertAliceBalancesInt(sdkmath.ZeroInt(), sdkmath.NewInt(99_999_967)) s.assertBobBalancesInt(sdkmath.NewInt(10000000), sdkmath.NewInt(23)) } func (s *DexTestSuite) TestPlaceLimitOrderWithPrice1To0() { - s.fundAliceBalances(0, 100) - s.fundBobBalances(25, 0) + s.fundAliceBalances(0, 200) + s.fundBobBalances(10, 0) var price = math_utils.MustNewPrecDecFromStr("0.25") // GIVEN // Alice place LO at price ~.25 - trancheKey0 := s.limitSellsWithPrice(s.alice, "TokenB", price, 100) + trancheKey0 := s.limitSellsWithPrice(s.alice, "TokenB", price, 200) // WHEN bob swaps through all of Alice's LO - s.limitSellsWithPrice(s.bob, "TokenA", price, 25) + s.limitSellsWithPrice(s.bob, "TokenA", math_utils.OnePrecDec().Quo(price), 10) s.aliceWithdrawsLimitSell(trancheKey0) - // THEN alice gets out ~25 TOKENA and bob pays ~25 TOKENB - s.assertAliceBalancesInt(sdkmath.NewInt(25000000), sdkmath.ZeroInt()) - s.assertBobBalancesInt(sdkmath.ZeroInt(), sdkmath.NewInt(25000000)) + // THEN alice gets out ~10 TOKENA and bob gets ~40 TOKENB + s.assertAliceBalancesInt(sdkmath.NewInt(9999998), sdkmath.ZeroInt()) + s.assertBobBalancesInt(sdkmath.ZeroInt(), sdkmath.NewInt(40001452)) } // Fill Or Kill limit orders /////////////////////////////////////////////////////////// diff --git a/x/dex/types/errors.go b/x/dex/types/errors.go index 93cac1cfe..fa6c55b27 100644 --- a/x/dex/types/errors.go +++ b/x/dex/types/errors.go @@ -200,4 +200,10 @@ var ( 1156, "Invalid price; 0.00000000000000000000000050 < PRICE > 2020125331305056766451886.728", ) + + ErrInvalidPriceAndTick = sdkerrors.Register( + ModuleName, + 1157, + "Cannot specify PriceInToOut and TickIndexInToOut", + ) ) diff --git a/x/dex/types/message_place_limit_order.go b/x/dex/types/message_place_limit_order.go index 5b3c4e83a..c460e434d 100644 --- a/x/dex/types/message_place_limit_order.go +++ b/x/dex/types/message_place_limit_order.go @@ -6,6 +6,7 @@ import ( sdkerrors "cosmossdk.io/errors" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + math_utils "github.com/neutron-org/neutron/v3/utils/math" ) const TypeMsgPlaceLimitOrder = "place_limit_order" @@ -22,6 +23,7 @@ func NewMsgPlaceLimitOrder( orderType LimitOrderType, goodTil *time.Time, maxAmountOut *math.Int, + price *math_utils.PrecDec, ) *MsgPlaceLimitOrder { return &MsgPlaceLimitOrder{ Creator: creator, @@ -33,6 +35,7 @@ func NewMsgPlaceLimitOrder( OrderType: orderType, ExpirationTime: goodTil, MaxAmountOut: maxAmountOut, + PriceInToOut: price, } } @@ -94,6 +97,14 @@ func (msg *MsgPlaceLimitOrder) ValidateBasic() error { return ErrTickOutsideRange } + if msg.PriceInToOut != nil && IsPriceOutOfRange(*msg.PriceInToOut) { + return ErrPriceOutsideRange + } + + if msg.PriceInToOut != nil && msg.TickIndexInToOut != 0 { + return ErrInvalidPriceAndTick + } + return nil } diff --git a/x/dex/types/message_place_limit_order_test.go b/x/dex/types/message_place_limit_order_test.go index 6ece75a93..ed1303f98 100644 --- a/x/dex/types/message_place_limit_order_test.go +++ b/x/dex/types/message_place_limit_order_test.go @@ -4,6 +4,7 @@ import ( "testing" "cosmossdk.io/math" + math_utils "github.com/neutron-org/neutron/v3/utils/math" "github.com/stretchr/testify/require" "github.com/neutron-org/neutron/v3/testutil/common/sample" @@ -13,6 +14,11 @@ import ( func TestMsgPlaceLimitOrder_ValidateBasic(t *testing.T) { ZEROINT := math.ZeroInt() ONEINT := math.OneInt() + FIVEDEC := math_utils.NewPrecDec(6) + SMALLDEC := math_utils.MustNewPrecDecFromStr("0.02") + TINYDEC := math_utils.MustNewPrecDecFromStr("0.000000000000000000000000494") + HUGEDEC := math_utils.MustNewPrecDecFromStr("2020125331305056766452345.127500016657360222036663652") + tests := []struct { name string msg dextypes.MsgPlaceLimitOrder @@ -109,7 +115,44 @@ func TestMsgPlaceLimitOrder_ValidateBasic(t *testing.T) { err: dextypes.ErrTickOutsideRange, }, { - name: "valid msg", + name: "price < minPrice", + msg: dextypes.MsgPlaceLimitOrder{ + Creator: sample.AccAddress(), + Receiver: sample.AccAddress(), + TokenIn: "TokenA", + TokenOut: "TokenB", + PriceInToOut: &TINYDEC, + AmountIn: math.OneInt(), + }, + err: dextypes.ErrPriceOutsideRange, + }, + { + name: "price > maxPrice", + msg: dextypes.MsgPlaceLimitOrder{ + Creator: sample.AccAddress(), + Receiver: sample.AccAddress(), + TokenIn: "TokenA", + TokenOut: "TokenB", + PriceInToOut: &HUGEDEC, + AmountIn: math.OneInt(), + }, + err: dextypes.ErrPriceOutsideRange, + }, + { + name: "price > maxPrice", + msg: dextypes.MsgPlaceLimitOrder{ + Creator: sample.AccAddress(), + Receiver: sample.AccAddress(), + TokenIn: "TokenA", + TokenOut: "TokenB", + PriceInToOut: &FIVEDEC, + TickIndexInToOut: 6, + AmountIn: math.OneInt(), + }, + err: dextypes.ErrInvalidPriceAndTick, + }, + { + name: "valid msg tick", msg: dextypes.MsgPlaceLimitOrder{ Creator: sample.AccAddress(), Receiver: sample.AccAddress(), @@ -119,6 +162,40 @@ func TestMsgPlaceLimitOrder_ValidateBasic(t *testing.T) { AmountIn: math.OneInt(), }, }, + + { + name: "valid msg price", + msg: dextypes.MsgPlaceLimitOrder{ + Creator: sample.AccAddress(), + Receiver: sample.AccAddress(), + TokenIn: "TokenA", + TokenOut: "TokenB", + AmountIn: math.OneInt(), + PriceInToOut: &FIVEDEC, + }, + }, + { + name: "valid msg price > 1", + msg: dextypes.MsgPlaceLimitOrder{ + Creator: sample.AccAddress(), + Receiver: sample.AccAddress(), + TokenIn: "TokenA", + TokenOut: "TokenB", + AmountIn: math.OneInt(), + PriceInToOut: &FIVEDEC, + }, + }, + { + name: "valid msg price < 1", + msg: dextypes.MsgPlaceLimitOrder{ + Creator: sample.AccAddress(), + Receiver: sample.AccAddress(), + TokenIn: "TokenA", + TokenOut: "TokenB", + AmountIn: math.OneInt(), + PriceInToOut: &SMALLDEC, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {