Skip to content

Commit

Permalink
Merge pull request #196 from crescent-network/fix/limit-market-order-…
Browse files Browse the repository at this point in the history
…price

fix: limit market orders' price impact to MaxOrderPriceRatio
  • Loading branch information
kingcre authored Sep 5, 2023
2 parents 2f97b36 + 38f9982 commit a976453
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 22 deletions.
3 changes: 2 additions & 1 deletion x/amm/keeper/farming_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ func (s *KeeperTestSuite) TestFarming() {
s.Collect(lpAddr2, position2.Id, utils.ParseCoins("47uatom"))

ordererAddr := s.FundedAccount(3, utils.ParseCoins("10000_000000uusd"))
s.PlaceMarketOrder(pool.MarketId, ordererAddr, true, sdk.NewDec(120_000000))
s.PlaceLimitOrder(
pool.MarketId, ordererAddr, true, utils.ParseDec("6"), sdk.NewDec(120_000000), 0)

poolState := s.App.AMMKeeper.MustGetPoolState(s.Ctx, pool.Id)
fmt.Println(poolState.CurrentPrice)
Expand Down
15 changes: 10 additions & 5 deletions x/amm/keeper/invariants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (s *KeeperTestSuite) TestRewardsGrowthGlobalInvariant() {
s.NextBlock()

ordererAddr := s.FundedAccount(2, enoughCoins)
s.PlaceMarketOrder(market.Id, ordererAddr, true, sdk.NewDec(200_000000))
s.PlaceLimitOrder(market.Id, ordererAddr, true, utils.ParseDec("1"), sdk.NewDec(200_000000), 0)
s.PlaceMarketOrder(market.Id, ordererAddr, false, sdk.NewDec(500_000000))

_, broken := keeper.RewardsGrowthGlobalInvariant(s.keeper)(s.Ctx)
Expand All @@ -53,7 +53,7 @@ func (s *KeeperTestSuite) TestRewardsGrowthOutsideInvariant() {
}, utils.ParseTime("2023-01-01T00:00:00Z"), utils.ParseTime("2024-01-01T00:00:00Z"))
lpAddr := s.FundedAccount(1, enoughCoins)
s.AddLiquidity(
lpAddr, pool.Id, utils.ParseDec("0.4"), utils.ParseDec("0.6"),
lpAddr, pool.Id, utils.ParseDec("0.47"), utils.ParseDec("0.53"),
utils.ParseCoins("100_000000ucre,50_000000uusd"))
s.AddLiquidity(
lpAddr, pool.Id, utils.ParseDec("0.001"), utils.ParseDec("1000"),
Expand All @@ -62,8 +62,10 @@ func (s *KeeperTestSuite) TestRewardsGrowthOutsideInvariant() {
s.NextBlock()

ordererAddr := s.FundedAccount(2, enoughCoins)
s.PlaceMarketOrder(market.Id, ordererAddr, true, sdk.NewDec(200_000000))
s.PlaceMarketOrder(market.Id, ordererAddr, false, sdk.NewDec(500_000000))
s.PlaceLimitOrder(market.Id, ordererAddr, true, utils.ParseDec("1"), sdk.NewDec(200_000000), 0)
s.PlaceMarketOrder(market.Id, ordererAddr, false, sdk.NewDec(200_000000))
s.PlaceMarketOrder(market.Id, ordererAddr, false, sdk.NewDec(200_000000))
s.PlaceMarketOrder(market.Id, ordererAddr, false, sdk.NewDec(200_000000))

_, broken := keeper.RewardsGrowthOutsideInvariant(s.keeper)(s.Ctx)
s.Require().False(broken)
Expand All @@ -80,6 +82,9 @@ func (s *KeeperTestSuite) TestRewardsGrowthOutsideInvariant() {

func (s *KeeperTestSuite) TestCanCollectInvariant() {
market, pool := s.CreateMarketAndPool("ucre", "uusd", utils.ParseDec("0.5"))
marketState := s.App.ExchangeKeeper.MustGetMarketState(s.Ctx, market.Id)
marketState.LastPrice = utils.ParseDecP("0.5")
s.App.ExchangeKeeper.SetMarketState(s.Ctx, market.Id, marketState)
farmingPoolAddr := s.FundedAccount(0, enoughCoins)
s.CreatePublicFarmingPlan(
"Farming plan", farmingPoolAddr, farmingPoolAddr, []types.FarmingRewardAllocation{
Expand Down Expand Up @@ -160,7 +165,7 @@ func (s *KeeperTestSuite) TestPoolCurrentLiquidityInvariant() {
utils.ParseCoins("1000_000000ucre,500_000000uusd"))

ordererAddr := s.FundedAccount(2, enoughCoins)
s.PlaceMarketOrder(market.Id, ordererAddr, true, sdk.NewDec(200_000000))
s.PlaceLimitOrder(market.Id, ordererAddr, true, utils.ParseDec("1"), sdk.NewDec(200_000000), 0)

_, broken := keeper.PoolCurrentLiquidityInvariant(s.keeper)(s.Ctx)
s.Require().False(broken)
Expand Down
7 changes: 3 additions & 4 deletions x/amm/keeper/order_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package keeper_test

import (
"fmt"
"testing"
"time"

Expand Down Expand Up @@ -116,8 +115,8 @@ func (s *KeeperTestSuite) TestOrderGas() {
}
ordererAddr := s.FundedAccount(2, enoughCoins)
gasConsumedBefore := s.Ctx.GasMeter().GasConsumed()
s.PlaceMarketOrder(market.Id, ordererAddr, true, sdk.NewDec(50_000000))
fmt.Println(s.Ctx.GasMeter().GasConsumed() - gasConsumedBefore)
s.PlaceLimitOrder(market.Id, ordererAddr, true, utils.ParseDec("150.5"), sdk.NewDec(50_000000), 0)
s.Require().Less(s.Ctx.GasMeter().GasConsumed()-gasConsumedBefore, uint64(200000))
}

func (s *KeeperTestSuite) TestCurrentLiquidityEdgecase() {
Expand All @@ -134,7 +133,7 @@ func (s *KeeperTestSuite) TestCurrentLiquidityEdgecase() {
ordererAddr := s.FundedAccount(2, enoughCoins)
s.PlaceLimitOrder(market.Id, ordererAddr, true, utils.ParseDec("4.99"), sdk.NewDec(5988301+1), time.Hour)

s.PlaceMarketOrder(market.Id, ordererAddr, false, sdk.NewDec(5979313+5988301*2+1000))
s.PlaceLimitOrder(market.Id, ordererAddr, false, utils.ParseDec("4.8"), sdk.NewDec(5979313+5988301*2+1000), 0)
_, broken := keeper.PoolCurrentLiquidityInvariant(s.keeper)(s.Ctx)
s.Require().False(broken)
}
8 changes: 4 additions & 4 deletions x/amm/keeper/position_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (s *KeeperTestSuite) TestReinitializePosition() {
ownerAddr, pool.Id, lowerPrice, upperPrice, desiredAmt)

ordererAddr := s.FundedAccount(2, enoughCoins)
s.PlaceMarketOrder(market.Id, ordererAddr, true, sdk.NewDec(1000000))
s.PlaceLimitOrder(market.Id, ordererAddr, true, utils.ParseDec("6"), sdk.NewDec(1000000), 0)
s.PlaceMarketOrder(market.Id, ordererAddr, false, sdk.NewDec(1000000))

s.RemoveLiquidity(ownerAddr, position.Id, liquidity)
Expand All @@ -51,7 +51,7 @@ func (s *KeeperTestSuite) TestRemoveAllAndCollect() {

// Accrue fees.
ordererAddr := s.FundedAccount(2, enoughCoins)
s.PlaceMarketOrder(market.Id, ordererAddr, true, sdk.NewDec(10_000000))
s.PlaceLimitOrder(market.Id, ordererAddr, true, utils.ParseDec("6"), sdk.NewDec(10_000000), 0)
s.PlaceMarketOrder(market.Id, ordererAddr, false, sdk.NewDec(10_000000))

s.RemoveLiquidity(lpAddr, position.Id, position.Liquidity)
Expand Down Expand Up @@ -97,8 +97,8 @@ func (s *KeeperTestSuite) TestRewardsPool() {
lpAddr, pool2.Id, utils.ParseDec("9"), utils.ParseDec("12"), utils.ParseCoins("100_000000uatom,1000_000000uusd"))

ordererAddr := s.FundedAccount(2, enoughCoins)
s.PlaceMarketOrder(market1.Id, ordererAddr, true, sdk.NewDec(1_000000))
s.PlaceMarketOrder(market2.Id, ordererAddr, false, sdk.NewDec(1_000000))
s.PlaceLimitOrder(market1.Id, ordererAddr, true, utils.ParseDec("6"), sdk.NewDec(1_000000),0 )
s.PlaceLimitOrder(market2.Id, ordererAddr, false, utils.ParseDec("9"), sdk.NewDec(1_000000), 0)

s.AssertEqual(utils.ParseCoins("1499ucre,2620uusd"), s.GetAllBalances(pool1.MustGetRewardsPoolAddress()))
s.AssertEqual(utils.ParseCoins("268uatom,14982uusd"), s.GetAllBalances(pool2.MustGetRewardsPoolAddress()))
Expand Down
4 changes: 4 additions & 0 deletions x/amm/keeper/sim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ func (s *KeeperTestSuite) TestSimulation() {
initialPrice = utils.RandomDec(r, utils.ParseDec("100000"), maxPrice)
}
market, pool := s.CreateMarketAndPool("ucre", "uusd", initialPrice)
marketState := s.App.ExchangeKeeper.MustGetMarketState(s.Ctx, market.Id)
lastPrice := exchangetypes.PriceAtTick(exchangetypes.TickAtPrice(initialPrice))
marketState.LastPrice = &lastPrice
s.App.ExchangeKeeper.SetMarketState(s.Ctx, market.Id, marketState)

lpAddrs := make([]sdk.AccAddress, 10)
for i := range lpAddrs {
Expand Down
15 changes: 14 additions & 1 deletion x/exchange/keeper/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,16 +198,29 @@ func (k Keeper) PlaceMarketOrder(
err = sdkerrors.Wrap(sdkerrors.ErrNotFound, "market not found")
return
}
marketState := k.MustGetMarketState(ctx, market.Id)
if marketState.LastPrice == nil {
err = sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "market has no last price")
return
}
maxPriceRatio := k.GetMaxOrderPriceRatio(ctx)

orderId = k.GetNextOrderIdWithUpdate(ctx)
var quoteLimit *sdk.Dec
var (
quoteLimit *sdk.Dec
priceLimit sdk.Dec
)
if isBuy {
quote := k.bankKeeper.SpendableCoins(ctx, ordererAddr).AmountOf(market.QuoteDenom).ToDec()
quoteLimit = &quote
priceLimit = marketState.LastPrice.Mul(utils.OneDec.Add(maxPriceRatio))
} else {
priceLimit = marketState.LastPrice.Mul(utils.OneDec.Sub(maxPriceRatio))
}
res, err = k.executeOrder(
ctx, market, ordererAddr, types.MemOrderBookSideOptions{
IsBuy: !isBuy,
PriceLimit: &priceLimit,
QuantityLimit: &qty,
QuoteLimit: quoteLimit,
}, false, false)
Expand Down
16 changes: 10 additions & 6 deletions x/exchange/keeper/order_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ func (s *KeeperTestSuite) TestPlaceMMLimitOrder() {
s.Ctx, market.Id, ordererAddr1, true, utils.ParseDec("5.1"), sdk.NewDec(10_00000), time.Hour)
s.Require().EqualError(err, "16 > 15: number of MM orders exceeded the limit")

s.PlaceMarketOrder(market.Id, ordererAddr2, false, sdk.NewDec(30_000000))
s.PlaceLimitOrder(
market.Id, ordererAddr2, false, utils.ParseDec("4.9"), sdk.NewDec(30_000000), 0)

s.PlaceMMLimitOrder(
market.Id, ordererAddr1, true, utils.ParseDec("4.9"), sdk.NewDec(10_00000), time.Hour)
Expand All @@ -134,7 +135,8 @@ func (s *KeeperTestSuite) TestPlaceMMBatchLimitOrder() {
s.Ctx, market.Id, ordererAddr1, true, utils.ParseDec("5.1"), sdk.NewDec(10_00000), time.Hour)
s.Require().EqualError(err, "16 > 15: number of MM orders exceeded the limit")

s.PlaceMarketOrder(market.Id, ordererAddr2, false, sdk.NewDec(30_000000))
s.PlaceLimitOrder(
market.Id, ordererAddr2, false, utils.ParseDec("4.9"), sdk.NewDec(30_000000), 0)
s.NextBlock()

s.PlaceMMBatchLimitOrder(
Expand Down Expand Up @@ -246,7 +248,8 @@ func (s *KeeperTestSuite) TestFairMatching() {
_, order2, _ := s.PlaceLimitOrder(
market.Id, ordererAddr2, true, utils.ParseDec("1.2"), sdk.NewDec(5000), 0)

s.PlaceMarketOrder(market.Id, ordererAddr3, false, sdk.NewDec(9000))
s.PlaceLimitOrder(
market.Id, ordererAddr3, false, utils.ParseDec("1.1"), sdk.NewDec(9000), 0)

order1, _ = s.keeper.GetOrder(s.Ctx, order1.Id)
order2, _ = s.keeper.GetOrder(s.Ctx, order2.Id)
Expand All @@ -257,9 +260,9 @@ func (s *KeeperTestSuite) TestFairMatching() {
s.NextBlock()

_, order1, _ = s.PlaceLimitOrder(
market.Id, ordererAddr1, true, utils.ParseDec("0.3"), sdk.NewDec(7000), 0)
market.Id, ordererAddr1, true, utils.ParseDec("1.1"), sdk.NewDec(7000), 0)
_, order2, _ = s.PlaceLimitOrder(
market.Id, ordererAddr2, true, utils.ParseDec("0.3"), sdk.NewDec(3000), 0)
market.Id, ordererAddr2, true, utils.ParseDec("1.1"), sdk.NewDec(3000), 0)

s.PlaceMarketOrder(market.Id, ordererAddr3, false, sdk.NewDec(101))

Expand All @@ -286,7 +289,8 @@ func (s *KeeperTestSuite) TestDecQuantity() {
_, order2, _ := s.PlaceLimitOrder(
market.Id, ordererAddr2, true, utils.ParseDec("0.14076"), sdk.NewDec(3000), time.Hour)

s.PlaceMarketOrder(market.Id, ordererAddr3, false, sdk.NewDec(1001))
s.PlaceLimitOrder(
market.Id, ordererAddr3, false, utils.ParseDec("0.14"), sdk.NewDec(1001), 0)

order1, _ = s.keeper.GetOrder(s.Ctx, order1.Id)
order2, _ = s.keeper.GetOrder(s.Ctx, order2.Id)
Expand Down
4 changes: 4 additions & 0 deletions x/exchange/simulation/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,10 @@ func findMsgPlaceMarketOrderParams(
accs = utils.ShuffleSimAccounts(r, accs)
var markets []types.Market
k.IterateAllMarkets(ctx, func(market types.Market) (stop bool) {
marketState := k.MustGetMarketState(ctx, market.Id)
if marketState.LastPrice == nil { // skip markets with no last price
return false
}
markets = append(markets, market)
return false
})
Expand Down
5 changes: 4 additions & 1 deletion x/exchange/simulation/operations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ func (s *SimTestSuite) TestSimulateMsgPlaceMarketOrder() {
r := rand.New(rand.NewSource(0))
accs := s.getTestingAccounts(r, 50)

s.CreateMarket("denom1", "denom2")
market := s.CreateMarket("denom1", "denom2")
marketState := s.keeper.MustGetMarketState(s.Ctx, market.Id)
marketState.LastPrice = utils.ParseDecP("1.2")
s.keeper.SetMarketState(s.Ctx, market.Id, marketState)

op := simulation.SimulateMsgPlaceMarketOrder(
s.App.AccountKeeper, s.App.BankKeeper, s.keeper)
Expand Down

0 comments on commit a976453

Please sign in to comment.