Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add pool min_order_quote #185

Merged
merged 3 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/docs/statik/statik.go

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions client/docs/swagger-ui/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12651,6 +12651,8 @@ paths:
format: int64
min_order_quantity:
type: string
min_order_quote:
type: string
current_tick:
type: integer
format: int32
Expand Down Expand Up @@ -12867,6 +12869,8 @@ paths:
format: int64
min_order_quantity:
type: string
min_order_quote:
type: string
current_tick:
type: integer
format: int32
Expand Down Expand Up @@ -20764,6 +20768,8 @@ definitions:
format: int64
min_order_quantity:
type: string
min_order_quote:
type: string
current_tick:
type: integer
format: int32
Expand Down Expand Up @@ -21025,6 +21031,8 @@ definitions:
format: int64
min_order_quantity:
type: string
min_order_quote:
type: string
current_tick:
type: integer
format: int32
Expand Down Expand Up @@ -21470,6 +21478,8 @@ definitions:
format: int64
min_order_quantity:
type: string
min_order_quote:
type: string
current_tick:
type: integer
format: int32
Expand Down
2 changes: 2 additions & 0 deletions proto/crescent/amm/v1beta1/amm.proto
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ message Pool {
uint32 tick_spacing = 7;
string min_order_quantity = 8
[(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string min_order_quote = 9
[(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}

message PoolState {
Expand Down
1 change: 1 addition & 0 deletions proto/crescent/amm/v1beta1/event.proto
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,5 @@ message EventPoolParameterChanged {
uint64 pool_id = 1;
uint32 tick_spacing = 2;
string min_order_quantity = 3 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec"];
string min_order_quote = 4 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec"];
}
1 change: 1 addition & 0 deletions proto/crescent/amm/v1beta1/proposal.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ message PoolParameterChange {
uint64 pool_id = 1;
uint32 tick_spacing = 2;
string min_order_quantity = 3 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec"];
string min_order_quote = 4 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec"];
}
14 changes: 8 additions & 6 deletions proto/crescent/amm/v1beta1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -182,16 +182,18 @@ message PoolResponse {
uint32 tick_spacing = 7;
string min_order_quantity = 8
[(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
int32 current_tick = 9;
string current_price = 10
string min_order_quote = 9
[(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string current_liquidity = 11
int32 current_tick = 10;
string current_price = 11
[(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string current_liquidity = 12
[(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
string total_liquidity = 12
string total_liquidity = 13
[(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
repeated cosmos.base.v1beta1.DecCoin fee_growth_global = 13
repeated cosmos.base.v1beta1.DecCoin fee_growth_global = 14
[(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins", (gogoproto.nullable) = false];
repeated cosmos.base.v1beta1.DecCoin farming_rewards_growth_global = 14
repeated cosmos.base.v1beta1.DecCoin farming_rewards_growth_global = 15
[(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins", (gogoproto.nullable) = false];
}

Expand Down
79 changes: 52 additions & 27 deletions x/amm/keeper/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,45 +55,34 @@ func (k Keeper) IteratePoolOrders(ctx sdk.Context, pool types.Pool, isBuy bool,
reserveBalance := k.bankKeeper.SpendableCoins(ctx, pool.MustGetReserveAddress()).
AmountOf(pool.DenomOut(isBuy)).ToDec()
orderLiquidity := poolState.CurrentLiquidity
currentSqrtPrice := utils.DecApproxSqrt(poolState.CurrentPrice)
currentPrice := poolState.CurrentPrice

iterCb := func(tick int32, tickInfo types.TickInfo) (stop bool) {
if orderLiquidity.IsPositive() {
prevTick := poolState.CurrentTick
for {
if !reserveBalance.IsPositive() {
return true
}

var orderTick int32
var (
orderTick int32
valid bool
)
if isBuy {
// L^2 + 4 * MinQty * L * sqrt(P_current)
intermediate := orderLiquidity.ToDec().Power(2).Add(
pool.MinOrderQuantity.MulInt(orderLiquidity).MulTruncate(currentSqrtPrice).MulInt64(4))
orderSqrtPrice := utils.DecApproxSqrt(intermediate).Sub(orderLiquidity.ToDec()).
QuoTruncate(pool.MinOrderQuantity.MulInt64(2))
orderPrice := orderSqrtPrice.Power(2)
orderTick = types.AdjustPriceToTickSpacing(orderPrice, pool.TickSpacing, false)
if orderTick >= prevTick {
orderTick = types.AdjustTickToTickSpacing(prevTick, pool.TickSpacing, false) - int32(pool.TickSpacing)
}
if orderTick < tick {
orderTick, valid = NextOrderTick(
true, orderLiquidity, currentPrice, pool.MinOrderQuantity, pool.MinOrderQuote, pool.TickSpacing)
if !valid || orderTick < tick {
orderTick = tick
}
} else {
orderSqrtPrice := currentSqrtPrice.MulInt(orderLiquidity).
QuoRoundUp(orderLiquidity.ToDec().Sub(pool.MinOrderQuantity.Mul(currentSqrtPrice)))
orderPrice := orderSqrtPrice.Power(2)
orderTick = types.AdjustPriceToTickSpacing(orderPrice, pool.TickSpacing, true)
if orderTick <= prevTick {
orderTick = types.AdjustTickToTickSpacing(prevTick, pool.TickSpacing, true) + int32(pool.TickSpacing)
}
if orderTick > tick {
orderTick, valid = NextOrderTick(
false, orderLiquidity, currentPrice, pool.MinOrderQuantity, pool.MinOrderQuote, pool.TickSpacing)
if !valid || orderTick > tick {
orderTick = tick
}
}
orderPrice := exchangetypes.PriceAtTick(orderTick)
orderSqrtPrice := utils.DecApproxSqrt(orderPrice)
currentSqrtPrice := utils.DecApproxSqrt(currentPrice)
var qty sdk.Dec
if isBuy {
qty = sdk.MinDec(
Expand All @@ -109,16 +98,14 @@ func (k Keeper) IteratePoolOrders(ctx sdk.Context, pool types.Pool, isBuy bool,
return true
}
reserveBalance = reserveBalance.Sub(exchangetypes.DepositAmount(isBuy, orderPrice, qty))
currentSqrtPrice = orderSqrtPrice
currentPrice = orderPrice
}

if orderTick == tick {
break
}
prevTick = orderTick
}
} else {
currentSqrtPrice = types.SqrtPriceAtTick(tick)
currentPrice = exchangetypes.PriceAtTick(tick)
}
if isBuy {
orderLiquidity = orderLiquidity.Sub(tickInfo.NetLiquidity)
Expand All @@ -133,3 +120,41 @@ func (k Keeper) IteratePoolOrders(ctx sdk.Context, pool types.Pool, isBuy bool,
k.IterateTickInfosAbove(ctx, pool.Id, poolState.CurrentTick, iterCb)
}
}

func NextOrderTick(
isBuy bool, liquidity sdk.Int, currentPrice, minOrderQty, minOrderQuote sdk.Dec, tickSpacing uint32) (tick int32, valid bool) {
currentSqrtPrice := utils.DecApproxSqrt(currentPrice)
liquidityDec := liquidity.ToDec()
if isBuy {
// 1. Check min order qty
// L^2 + 4 * MinQty * L * sqrt(P_current)
intermediate := liquidityDec.Power(2).Add(
minOrderQty.Mul(liquidityDec).MulTruncate(currentSqrtPrice).MulInt64(4))
orderSqrtPrice := utils.DecApproxSqrt(intermediate).Sub(liquidityDec).QuoTruncate(minOrderQty.MulInt64(2))
if !orderSqrtPrice.IsPositive() || orderSqrtPrice.GTE(currentSqrtPrice) {
return 0, false
}
// 2. Check min order quote
orderSqrtPrice2 := currentSqrtPrice.Mul(liquidityDec).Sub(minOrderQuote).QuoTruncate(liquidityDec)
if !orderSqrtPrice2.IsPositive() || orderSqrtPrice2.GTE(currentSqrtPrice) {
return 0, false
}
orderPrice := sdk.MinDec(orderSqrtPrice, orderSqrtPrice2).Power(2)
tick = types.AdjustPriceToTickSpacing(orderPrice, tickSpacing, false)
return tick, true
}
// 1. Check min order qty
orderSqrtPrice := currentSqrtPrice.Mul(liquidityDec).
QuoRoundUp(liquidityDec.Sub(minOrderQty.Mul(currentSqrtPrice)))
if !orderSqrtPrice.IsPositive() || orderSqrtPrice.LTE(currentSqrtPrice) {
return 0, false
}
// 2. Check min order quote
orderSqrtPrice2 := minOrderQuote.Mul(currentSqrtPrice).QuoRoundUp(liquidityDec).Add(currentSqrtPrice)
if !orderSqrtPrice2.IsPositive() || orderSqrtPrice2.LTE(currentSqrtPrice) {
return 0, false
}
orderPrice := sdk.MaxDec(orderSqrtPrice, orderSqrtPrice2).Power(2)
tick = types.AdjustPriceToTickSpacing(orderPrice, tickSpacing, true)
return tick, true
}
43 changes: 35 additions & 8 deletions x/amm/keeper/pool_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ import (

func BenchmarkPoolOrders(b *testing.B) {
app := chain.Setup(false)
ctx := app.NewContext(false, tmproto.Header{
Height: 0,
Time: utils.ParseTime("2023-01-01T00:00:00Z"),
})
ctx := app.NewContext(false, tmproto.Header{})

creatorAddr := utils.TestAddress(0)
require.NoError(
Expand Down Expand Up @@ -61,10 +58,7 @@ func BenchmarkPoolOrders(b *testing.B) {
// XXX
func BenchmarkPlaceBuyMarketOrder(b *testing.B) {
app := chain.Setup(false)
ctx := app.NewContext(false, tmproto.Header{
Height: 0,
Time: utils.ParseTime("2023-01-01T00:00:00Z"),
})
ctx := app.NewContext(false, tmproto.Header{})

creatorAddr := utils.TestAddress(0)
require.NoError(
Expand Down Expand Up @@ -94,3 +88,36 @@ func BenchmarkPlaceBuyMarketOrder(b *testing.B) {
require.NoError(b, err)
}
}

func BenchmarkPoolOrdersSkewedPrice(b *testing.B) {
app := chain.Setup(false)
ctx := app.NewContext(false, tmproto.Header{})

creatorAddr := utils.TestAddress(0)
require.NoError(
b, chain.FundAccount(app.BankKeeper, ctx, creatorAddr, enoughCoins))

market, err := app.ExchangeKeeper.CreateMarket(ctx, creatorAddr, "ucre", "uusd")
require.NoError(b, err)

pool, err := app.AMMKeeper.CreatePool(ctx, creatorAddr, market.Id, utils.ParseDec("500"))
require.NoError(b, err)

lpAddr := utils.TestAddress(1)
require.NoError(b, chain.FundAccount(app.BankKeeper, ctx, lpAddr, enoughCoins))

_, _, _, err = app.AMMKeeper.AddLiquidity(
ctx, lpAddr, lpAddr, pool.Id, types.MinPrice, types.MaxPrice,
utils.ParseCoins("10_000000ucre,50_000000uusd"))
require.NoError(b, err)

ordererAddr := utils.TestAddress(2)
require.NoError(b, chain.FundAccount(app.BankKeeper, ctx, ordererAddr, enoughCoins))
b.ResetTimer()

for i := 0; i < b.N; i++ {
cacheCtx, _ := ctx.CacheContext()
_, _, err := app.ExchangeKeeper.PlaceMarketOrder(cacheCtx, market.Id, ordererAddr, true, sdk.NewDec(100_000000))
require.NoError(b, err)
}
}
9 changes: 7 additions & 2 deletions x/amm/keeper/proposal_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@ func HandlePoolParameterChangeProposal(ctx sdk.Context, k Keeper, p *types.PoolP
if change.MinOrderQuantity != nil {
pool.MinOrderQuantity = *change.MinOrderQuantity
}
if change.MinOrderQuote != nil {
pool.MinOrderQuote = *change.MinOrderQuote
}
k.SetPool(ctx, pool)
if err := ctx.EventManager().EmitTypedEvent(&types.EventPoolParameterChanged{
PoolId: change.PoolId,
TickSpacing: change.TickSpacing,
PoolId: change.PoolId,
TickSpacing: change.TickSpacing,
MinOrderQuantity: change.MinOrderQuantity,
MinOrderQuote: change.MinOrderQuote,
}); err != nil {
return err
}
Expand Down
22 changes: 17 additions & 5 deletions x/amm/keeper/proposal_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func (s *KeeperTestSuite) TestPoolParameterChangeProposal() {
// Change tick spacing only
proposal := types.NewPoolParameterChangeProposal(
"Title", "Description", []types.PoolParameterChange{
types.NewPoolParameterChange(pool.Id, 10, nil),
types.NewPoolParameterChange(pool.Id, 10, nil, nil),
})
s.Require().NoError(proposal.ValidateBasic())
s.Require().NoError(handler(s.Ctx, proposal))
Expand All @@ -24,30 +24,42 @@ func (s *KeeperTestSuite) TestPoolParameterChangeProposal() {
// Change min order qty only
proposal = types.NewPoolParameterChangeProposal(
"Title", "Description", []types.PoolParameterChange{
types.NewPoolParameterChange(pool.Id, 0, utils.ParseDecP("10000")),
types.NewPoolParameterChange(pool.Id, 0, utils.ParseDecP("10000"), nil),
})
s.Require().NoError(proposal.ValidateBasic())
s.Require().NoError(handler(s.Ctx, proposal))

pool, _ = s.keeper.GetPool(s.Ctx, pool.Id)
s.AssertEqual(utils.ParseDec("10000"), pool.MinOrderQuantity)

// Change both
// Change min order quote only
proposal = types.NewPoolParameterChangeProposal(
"Title", "Description", []types.PoolParameterChange{
types.NewPoolParameterChange(pool.Id, 5, utils.ParseDecP("1000000")),
types.NewPoolParameterChange(pool.Id, 0, nil, utils.ParseDecP("1000")),
})
s.Require().NoError(proposal.ValidateBasic())
s.Require().NoError(handler(s.Ctx, proposal))

pool, _ = s.keeper.GetPool(s.Ctx, pool.Id)
s.AssertEqual(utils.ParseDec("1000"), pool.MinOrderQuote)

// Change altogether
proposal = types.NewPoolParameterChangeProposal(
"Title", "Description", []types.PoolParameterChange{
types.NewPoolParameterChange(pool.Id, 5, utils.ParseDecP("1000000"), utils.ParseDecP("10000")),
})
s.Require().NoError(proposal.ValidateBasic())
s.Require().NoError(handler(s.Ctx, proposal))

pool, _ = s.keeper.GetPool(s.Ctx, pool.Id)
s.Require().EqualValues(5, pool.TickSpacing)
s.AssertEqual(utils.ParseDec("1000000"), pool.MinOrderQuantity)
s.AssertEqual(utils.ParseDec("10000"), pool.MinOrderQuote)

// Failing cases
proposal = types.NewPoolParameterChangeProposal(
"Title", "Description", []types.PoolParameterChange{
types.NewPoolParameterChange(pool.Id, 5, nil),
types.NewPoolParameterChange(pool.Id, 5, nil, nil),
})
s.Require().NoError(proposal.ValidateBasic())
// Same tick spacing
Expand Down
5 changes: 3 additions & 2 deletions x/amm/simulation/proposals.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ func SimulatePoolParameterChangeProposal(k keeper.Keeper) simtypes.ContentSimula
if len(allowedTickSpacings) > 0 {
tickSpacing = allowedTickSpacings[r.Intn(len(allowedTickSpacings))]
}
minOrderQty := utils.RandomDec(r, utils.ParseDec("1"), utils.ParseDec("10000"))
minOrderQty := utils.RandomDec(r, utils.ParseDec("1"), utils.ParseDec("1000000"))
minOrderQuote := utils.RandomDec(r, utils.ParseDec("1"), utils.ParseDec("1000000"))
changes = append(changes,
types.NewPoolParameterChange(pool.Id, tickSpacing, &minOrderQty))
types.NewPoolParameterChange(pool.Id, tickSpacing, &minOrderQty, &minOrderQuote))
}
return false
})
Expand Down
Loading
Loading