From 0e7e02aa786de9c456c49b14b98510552753522e Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Mon, 24 Oct 2022 16:17:54 -0500 Subject: [PATCH] concentrated liquidity: further swap/lp work (#3101) * further swap generalization * add step and swap state * Update test.yml * Update test.yml * Further generalize swaps and lp (#3111) * Change sqrt to caller * Remove margin of error from mint * Change Pool to have dec curr price * Remove swap structs * Adams comments * address code review comments * final additions * address code review comments * code review Co-authored-by: Matt, Park <45252226+mattverse@users.noreply.github.com> --- .github/workflows/test.yml | 2 + .../concentratedPool.proto | 30 +-- .../concentratedPool.pb.go | 60 ++--- x/concentrated-liquidity/export_test.go | 8 +- x/concentrated-liquidity/lp.go | 24 +- x/concentrated-liquidity/lp_test.go | 22 +- x/concentrated-liquidity/math.go | 40 ++- x/concentrated-liquidity/pool.go | 242 +++++++++++------- x/concentrated-liquidity/pool_test.go | 64 +++-- x/concentrated-liquidity/tick.go | 8 +- x/concentrated-liquidity/types/cl.go | 14 + 11 files changed, 316 insertions(+), 198 deletions(-) create mode 100644 x/concentrated-liquidity/types/cl.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b5ade8be72a..0108408cf93 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,7 @@ on: push: branches: - "main" + - "concentrated-liquidity-main" - "v[0-9]**" workflow_dispatch: @@ -124,3 +125,4 @@ jobs: - name: Test e2e and Upgrade run: make test-e2e-ci + diff --git a/proto/osmosis/concentrated-liquidity/concentratedPool.proto b/proto/osmosis/concentrated-liquidity/concentratedPool.proto index 79e724c4862..9ae361db297 100644 --- a/proto/osmosis/concentrated-liquidity/concentratedPool.proto +++ b/proto/osmosis/concentrated-liquidity/concentratedPool.proto @@ -11,47 +11,47 @@ import "gogoproto/gogo.proto"; option go_package = "github.com/osmosis-labs/osmosis/v12/x/concentrated-liquidity"; message Pool { - option (gogoproto.goproto_getters) = false; - option (gogoproto.goproto_stringer) = false; + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; option (cosmos_proto.implements_interface) = "PoolI"; - string address = 1 [(gogoproto.moretags) = "yaml:\"address\""]; - uint64 id = 2; + string address = 1 [ (gogoproto.moretags) = "yaml:\"address\"" ]; + uint64 id = 2; // Amount of total liquidity string liquidity = 3 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", - (gogoproto.moretags) = "yaml:\"liquidity\"", - (gogoproto.nullable) = false + (gogoproto.moretags) = "yaml:\"liquidity\"", + (gogoproto.nullable) = false ]; string token0 = 4; string token1 = 5; string current_sqrt_price = 6 [ - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", - (gogoproto.moretags) = "yaml:\"spot_price\"", - (gogoproto.nullable) = false + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.moretags) = "yaml:\"spot_price\"", + (gogoproto.nullable) = false ]; string current_tick = 7 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", - (gogoproto.moretags) = "yaml:\"current_tick\"", - (gogoproto.nullable) = false + (gogoproto.moretags) = "yaml:\"current_tick\"", + (gogoproto.nullable) = false ]; } message TickInfo { string liquidity = 1 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", - (gogoproto.moretags) = "yaml:\"liquidity\"", - (gogoproto.nullable) = false + (gogoproto.moretags) = "yaml:\"liquidity\"", + (gogoproto.nullable) = false ]; } message Position { string liquidity = 1 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", - (gogoproto.moretags) = "yaml:\"liquidity\"", - (gogoproto.nullable) = false + (gogoproto.moretags) = "yaml:\"liquidity\"", + (gogoproto.nullable) = false ]; } diff --git a/x/concentrated-liquidity/concentratedPool.pb.go b/x/concentrated-liquidity/concentratedPool.pb.go index 0e6b3017ed0..1969343f031 100644 --- a/x/concentrated-liquidity/concentratedPool.pb.go +++ b/x/concentrated-liquidity/concentratedPool.pb.go @@ -37,7 +37,7 @@ type Pool struct { Liquidity github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=liquidity,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"liquidity" yaml:"liquidity"` Token0 string `protobuf:"bytes,4,opt,name=token0,proto3" json:"token0,omitempty"` Token1 string `protobuf:"bytes,5,opt,name=token1,proto3" json:"token1,omitempty"` - CurrentSqrtPrice github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,6,opt,name=current_sqrt_price,json=currentSqrtPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"current_sqrt_price" yaml:"spot_price"` + CurrentSqrtPrice github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,6,opt,name=current_sqrt_price,json=currentSqrtPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"current_sqrt_price" yaml:"spot_price"` CurrentTick github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,7,opt,name=current_tick,json=currentTick,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"current_tick" yaml:"current_tick"` } @@ -158,35 +158,35 @@ func init() { } var fileDescriptor_b144264ce94bcf63 = []byte{ - // 436 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x93, 0x31, 0x8e, 0xd3, 0x40, - 0x14, 0x86, 0xed, 0x6c, 0x36, 0xcb, 0x0e, 0x68, 0xb5, 0x3b, 0x20, 0x64, 0xb6, 0xb0, 0x57, 0x96, - 0x40, 0x5b, 0x60, 0x1b, 0x83, 0x68, 0x22, 0xaa, 0x20, 0x8a, 0x74, 0x91, 0x41, 0x14, 0x34, 0xc1, - 0x1e, 0x0f, 0xc9, 0xc8, 0x8e, 0xc7, 0x9e, 0x99, 0x44, 0xe4, 0x06, 0x94, 0x94, 0x94, 0x39, 0x04, - 0x67, 0x40, 0x11, 0x55, 0x4a, 0x44, 0x61, 0xa1, 0xe4, 0x06, 0x39, 0x01, 0xb2, 0x3d, 0x31, 0x4e, - 0x41, 0x91, 0x22, 0x95, 0xfd, 0xfe, 0x37, 0xef, 0xff, 0x7e, 0xe9, 0xe9, 0x81, 0x97, 0x94, 0x4f, - 0x28, 0x27, 0xdc, 0x41, 0x34, 0x41, 0x38, 0x11, 0xcc, 0x17, 0x38, 0xb4, 0x62, 0x92, 0x4d, 0x49, - 0x48, 0xc4, 0x7c, 0x4f, 0x1e, 0x50, 0x1a, 0xdb, 0x29, 0xa3, 0x82, 0xc2, 0xc7, 0x72, 0xcc, 0x6e, - 0xf6, 0xeb, 0x29, 0x7b, 0xe6, 0x06, 0x58, 0xf8, 0xee, 0xf5, 0x23, 0x54, 0xbe, 0x1b, 0x96, 0x43, - 0x4e, 0x55, 0x54, 0x0e, 0xd7, 0x0f, 0x46, 0x74, 0x44, 0x2b, 0xbd, 0xf8, 0xab, 0x54, 0xf3, 0xc7, - 0x09, 0x68, 0x17, 0x18, 0xf8, 0x14, 0x9c, 0xf9, 0x61, 0xc8, 0x30, 0xe7, 0x9a, 0x7a, 0xa3, 0xde, - 0x9e, 0xf7, 0xe0, 0x36, 0x37, 0x2e, 0xe6, 0xfe, 0x24, 0xee, 0x9a, 0xb2, 0x61, 0x7a, 0xbb, 0x27, - 0xf0, 0x02, 0xb4, 0x48, 0xa8, 0xb5, 0x6e, 0xd4, 0xdb, 0xb6, 0xd7, 0x22, 0x21, 0xfc, 0x08, 0xce, - 0xeb, 0x30, 0xda, 0x49, 0x39, 0xdf, 0x5b, 0xe6, 0x86, 0xf2, 0x3b, 0x37, 0x9e, 0x8c, 0x88, 0x18, - 0x4f, 0x03, 0x1b, 0xd1, 0x89, 0x0c, 0x24, 0x3f, 0x16, 0x0f, 0x23, 0x47, 0xcc, 0x53, 0xcc, 0xed, - 0x7e, 0x22, 0xb6, 0xb9, 0x71, 0x59, 0xd1, 0x6a, 0x23, 0xd3, 0xfb, 0x67, 0x0a, 0x1f, 0x82, 0x8e, - 0xa0, 0x11, 0x4e, 0x9e, 0x69, 0xed, 0xc2, 0xde, 0x93, 0x55, 0xad, 0xbb, 0xda, 0x69, 0x43, 0x77, - 0x61, 0x06, 0x20, 0x9a, 0x32, 0x86, 0x13, 0x31, 0xe4, 0x19, 0x13, 0xc3, 0x94, 0x11, 0x84, 0xb5, - 0x4e, 0x19, 0xed, 0xf5, 0xc1, 0xd1, 0xae, 0xaa, 0x68, 0x3c, 0xa5, 0xd2, 0xc9, 0xf4, 0x2e, 0xa5, - 0xfd, 0xdb, 0x8c, 0x89, 0x41, 0x21, 0xc1, 0x31, 0xb8, 0xb7, 0x43, 0x0a, 0x82, 0x22, 0xed, 0xac, - 0x84, 0xbd, 0x39, 0x18, 0x76, 0xbf, 0x82, 0x35, 0xbd, 0x4c, 0xef, 0xae, 0x2c, 0xdf, 0x11, 0x14, - 0x75, 0xaf, 0xbe, 0x2c, 0x0c, 0xe5, 0xdb, 0xc2, 0x50, 0x7e, 0x7e, 0xb7, 0x4e, 0x8b, 0xf5, 0xf5, - 0xcd, 0x18, 0xdc, 0x29, 0x5a, 0xfd, 0xe4, 0x13, 0xdd, 0xdf, 0x86, 0x7a, 0x84, 0x6d, 0x14, 0xb4, - 0x01, 0xe5, 0x44, 0x10, 0x9a, 0x1c, 0x9f, 0xd6, 0x7b, 0xbf, 0x5c, 0xeb, 0xea, 0x6a, 0xad, 0xab, - 0x7f, 0xd6, 0xba, 0xfa, 0x75, 0xa3, 0x2b, 0xab, 0x8d, 0xae, 0xfc, 0xda, 0xe8, 0xca, 0x87, 0x57, - 0x0d, 0x80, 0xbc, 0x10, 0x2b, 0xf6, 0x03, 0xbe, 0x2b, 0x9c, 0x99, 0xfb, 0xdc, 0xf9, 0xfc, 0x9f, - 0x5b, 0x0b, 0x3a, 0xe5, 0x0d, 0xbc, 0xf8, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xaa, 0xa3, 0x98, 0x3b, - 0x94, 0x03, 0x00, 0x00, + // 441 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x53, 0x3d, 0x8e, 0xd3, 0x40, + 0x14, 0xb6, 0xb3, 0xd9, 0x2c, 0x3b, 0xa0, 0xd5, 0xee, 0x80, 0x90, 0xd9, 0xc2, 0x5e, 0x59, 0x02, + 0x6d, 0x81, 0x6d, 0x0c, 0xa2, 0x59, 0x51, 0x05, 0x28, 0xd2, 0x45, 0x06, 0x51, 0xd0, 0x04, 0x7b, + 0x3c, 0x24, 0x23, 0x3b, 0x1e, 0x7b, 0x66, 0x12, 0x91, 0x1b, 0x50, 0x52, 0x52, 0xe6, 0x10, 0x9c, + 0x01, 0x45, 0x54, 0x29, 0x11, 0x85, 0x85, 0x92, 0x1b, 0xe4, 0x04, 0xc8, 0xf6, 0xc4, 0x38, 0xc5, + 0x16, 0x29, 0x52, 0xd9, 0xef, 0x7b, 0xef, 0xfb, 0xd1, 0x3c, 0x3d, 0xf0, 0x92, 0xf2, 0x31, 0xe5, + 0x84, 0x3b, 0x88, 0x26, 0x08, 0x27, 0x82, 0xf9, 0x02, 0x87, 0x56, 0x4c, 0xb2, 0x09, 0x09, 0x89, + 0x98, 0xed, 0xc0, 0x7d, 0x4a, 0x63, 0x3b, 0x65, 0x54, 0x50, 0xf8, 0x58, 0xd2, 0xec, 0x66, 0xbf, + 0x66, 0xd9, 0x53, 0x37, 0xc0, 0xc2, 0x77, 0x2f, 0x1f, 0xa1, 0x72, 0x6e, 0x50, 0x92, 0x9c, 0xaa, + 0xa8, 0x14, 0x2e, 0x1f, 0x0c, 0xe9, 0x90, 0x56, 0x78, 0xf1, 0x57, 0xa1, 0xe6, 0xcf, 0x23, 0xd0, + 0x2e, 0x6c, 0xe0, 0x53, 0x70, 0xe2, 0x87, 0x21, 0xc3, 0x9c, 0x6b, 0xea, 0x95, 0x7a, 0x7d, 0xda, + 0x85, 0x9b, 0xdc, 0x38, 0x9b, 0xf9, 0xe3, 0xf8, 0xc6, 0x94, 0x0d, 0xd3, 0xdb, 0x8e, 0xc0, 0x33, + 0xd0, 0x22, 0xa1, 0xd6, 0xba, 0x52, 0xaf, 0xdb, 0x5e, 0x8b, 0x84, 0xf0, 0x13, 0x38, 0xad, 0xc3, + 0x68, 0x47, 0x25, 0xbf, 0xbb, 0xc8, 0x0d, 0xe5, 0x4f, 0x6e, 0x3c, 0x19, 0x12, 0x31, 0x9a, 0x04, + 0x36, 0xa2, 0x63, 0x19, 0x48, 0x7e, 0x2c, 0x1e, 0x46, 0x8e, 0x98, 0xa5, 0x98, 0xdb, 0xbd, 0x44, + 0x6c, 0x72, 0xe3, 0xbc, 0x72, 0xab, 0x85, 0x4c, 0xef, 0xbf, 0x28, 0x7c, 0x08, 0x3a, 0x82, 0x46, + 0x38, 0x79, 0xa6, 0xb5, 0x0b, 0x79, 0x4f, 0x56, 0x35, 0xee, 0x6a, 0xc7, 0x0d, 0xdc, 0x85, 0x19, + 0x80, 0x68, 0xc2, 0x18, 0x4e, 0xc4, 0x80, 0x67, 0x4c, 0x0c, 0x52, 0x46, 0x10, 0xd6, 0x3a, 0x65, + 0xb4, 0xd7, 0x7b, 0x44, 0x7b, 0x83, 0xd1, 0x26, 0x37, 0x2e, 0xaa, 0x68, 0x3c, 0xa5, 0x52, 0xc9, + 0xf4, 0xce, 0xa5, 0xfc, 0xbb, 0x8c, 0x89, 0x7e, 0x01, 0xc1, 0x11, 0xb8, 0xb7, 0xb5, 0x14, 0x04, + 0x45, 0xda, 0x49, 0x69, 0xf6, 0x76, 0xef, 0x77, 0xb8, 0x5f, 0x99, 0x35, 0xb5, 0x4c, 0xef, 0xae, + 0x2c, 0xdf, 0x13, 0x14, 0xdd, 0x5c, 0x7c, 0x9d, 0x1b, 0xca, 0xf7, 0xb9, 0xa1, 0xfc, 0xfa, 0x61, + 0x1d, 0x17, 0xeb, 0xeb, 0x99, 0x31, 0xb8, 0x53, 0xb4, 0x7a, 0xc9, 0x67, 0xba, 0xbb, 0x0d, 0xf5, + 0x00, 0xdb, 0x28, 0xdc, 0xfa, 0x94, 0x13, 0x41, 0x68, 0x72, 0x78, 0xb7, 0xee, 0x87, 0xc5, 0x4a, + 0x57, 0x97, 0x2b, 0x5d, 0xfd, 0xbb, 0xd2, 0xd5, 0x6f, 0x6b, 0x5d, 0x59, 0xae, 0x75, 0xe5, 0xf7, + 0x5a, 0x57, 0x3e, 0xbe, 0x6a, 0x18, 0xc8, 0x0b, 0xb1, 0x62, 0x3f, 0xe0, 0xdb, 0xc2, 0x99, 0xba, + 0xcf, 0x9d, 0x2f, 0xb7, 0xdc, 0x5a, 0xd0, 0x29, 0x6f, 0xe0, 0xc5, 0xbf, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x76, 0xc8, 0x10, 0x58, 0x94, 0x03, 0x00, 0x00, } func (m *Pool) Marshal() (dAtA []byte, err error) { diff --git a/x/concentrated-liquidity/export_test.go b/x/concentrated-liquidity/export_test.go index e7b83d55a7e..f52e111ddb8 100644 --- a/x/concentrated-liquidity/export_test.go +++ b/x/concentrated-liquidity/export_test.go @@ -1,6 +1,10 @@ package concentrated_liquidity +import ( + cltypes "github.com/osmosis-labs/osmosis/v12/x/concentrated-liquidity/types" +) + // OrderInitialPoolDenoms sets the pool denoms of a cl pool -func (p *Pool) OrderInitialPoolDenoms(denom0, denom1 string) error { - return p.orderInitialPoolDenoms(denom0, denom1) +func OrderInitialPoolDenoms(denom0, denom1 string) (string, string, error) { + return cltypes.OrderInitialPoolDenoms(denom0, denom1) } diff --git a/x/concentrated-liquidity/lp.go b/x/concentrated-liquidity/lp.go index 038af7ca2a9..a1bd7e27fdc 100644 --- a/x/concentrated-liquidity/lp.go +++ b/x/concentrated-liquidity/lp.go @@ -8,7 +8,7 @@ import ( types "github.com/osmosis-labs/osmosis/v12/x/concentrated-liquidity/types" ) -func (k Keeper) Mint(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, liquidityIn sdk.Int, lowerTick, upperTick int64) (amtDenom0, amtDenom1 sdk.Int, err error) { +func (k Keeper) Mint(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, liquidityIn sdk.Dec, lowerTick, upperTick int64) (amtDenom0, amtDenom1 sdk.Int, err error) { // ensure types.MinTick <= lowerTick < types.MaxTick // TODO (bez): Add unit tests. if lowerTick < types.MinTick || lowerTick >= types.MaxTick { @@ -24,26 +24,22 @@ func (k Keeper) Mint(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, liqui if liquidityIn.IsZero() { return sdk.Int{}, sdk.Int{}, fmt.Errorf("token in amount is zero") } - + // TODO: we need to check if TickInfo is initialized on a specific tick before updating, otherwise get wont work + // k.setTickInfo(ctx, poolId, lowerTick, TickInfo{}) // k.UpdateTickWithNewLiquidity(ctx, poolId, lowerTick, liquidityIn) + // k.setTickInfo(ctx, poolId, upperTick, TickInfo{}) // k.UpdateTickWithNewLiquidity(ctx, poolId, upperTick, liquidityIn) - - // k.updatePositionWithLiquidity(ctx, poolId, owner.String(), lowerTick, upperTick, liquidityIn) + // k.setPosition(ctx, poolId, owner, lowerTick, upperTick, Position{}) + // k.updatePositionWithLiquidity(ctx, poolId, owner, lowerTick, upperTick, liquidityIn) pool := k.getPoolbyId(ctx, poolId) currentSqrtPrice := pool.CurrentSqrtPrice - sqrtRatioUpperTick, err := k.getSqrtRatioAtTick(upperTick) - if err != nil { - return sdk.Int{}, sdk.Int{}, err - } - sqrtRatioLowerTick, err := k.getSqrtRatioAtTick(lowerTick) - if err != nil { - return sdk.Int{}, sdk.Int{}, err - } + sqrtRatioUpperTick, _ := k.tickToSqrtPrice(sdk.NewInt(upperTick)) + sqrtRatioLowerTick, _ := k.tickToSqrtPrice(sdk.NewInt(lowerTick)) - amtDenom0 = calcAmount0Delta(currentSqrtPrice.ToDec(), sqrtRatioUpperTick, liquidityIn.ToDec()).RoundInt() - amtDenom1 = calcAmount1Delta(currentSqrtPrice.ToDec(), sqrtRatioLowerTick, liquidityIn.ToDec()).RoundInt() + amtDenom0 = calcAmount0Delta(liquidityIn, currentSqrtPrice, sqrtRatioUpperTick).RoundInt() + amtDenom1 = calcAmount1Delta(liquidityIn, currentSqrtPrice, sqrtRatioLowerTick).RoundInt() return amtDenom0, amtDenom1, nil } diff --git a/x/concentrated-liquidity/lp_test.go b/x/concentrated-liquidity/lp_test.go index e8c5c4c00b0..40938cb3afe 100644 --- a/x/concentrated-liquidity/lp_test.go +++ b/x/concentrated-liquidity/lp_test.go @@ -9,25 +9,29 @@ func (s *KeeperTestSuite) TestMint() { // current tick: 85176 // lower tick: 84222 // upper tick: 86129 - // liquidity(token in):1517882343751509868544 - // current sqrt price: 5602277097478614198912276234240 - // denom0: uosmo + // liquidity(token in):1517882323 + // current sqrt price: 70710678 + // denom0: eth // denom1: usdc poolId := uint64(1) currentTick := sdk.NewInt(85176) lowerTick := int64(84222) upperTick := int64(86129) - liquidity, ok := sdk.NewIntFromString("1517882343751509868544") - s.Require().True(ok) - currentSqrtP, ok := sdk.NewIntFromString("5602277097478614198912276234240") - s.Require().True(ok) - denom0 := "uosmo" + liquidity, err := sdk.NewDecFromStr("1517.882323") + s.Require().NoError(err) + // currentSqrtP, ok := sdk.NewIntFromString("70710678") + currentSqrtP, err := sdk.NewDecFromStr("70.710678") + s.Require().NoError(err) + denom0 := "eth" denom1 := "usdc" s.SetupTest() s.App.ConcentratedLiquidityKeeper.CreateNewConcentratedLiquidityPool(s.Ctx, poolId, denom0, denom1, currentSqrtP, currentTick) - _, _, err := s.App.ConcentratedLiquidityKeeper.Mint(s.Ctx, poolId, s.TestAccs[0], liquidity, lowerTick, upperTick) + asset0, asset1, err := s.App.ConcentratedLiquidityKeeper.Mint(s.Ctx, poolId, s.TestAccs[0], liquidity, lowerTick, upperTick) s.Require().NoError(err) + + s.Require().Equal(sdk.NewInt(1), asset0) + s.Require().Equal(sdk.NewInt(5000), asset1) } diff --git a/x/concentrated-liquidity/math.go b/x/concentrated-liquidity/math.go index 0cc8405df7a..d35bd59a728 100644 --- a/x/concentrated-liquidity/math.go +++ b/x/concentrated-liquidity/math.go @@ -1,6 +1,8 @@ package concentrated_liquidity -import sdk "github.com/cosmos/cosmos-sdk/types" +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) // liquidity0 takes an amount of asset0 in the pool as well as the sqrtpCur and the nextPrice // sqrtPriceA is the smaller of sqrtpCur and the nextPrice @@ -52,3 +54,39 @@ func calcAmount1Delta(liq, sqrtPriceA, sqrtPriceB sdk.Dec) sdk.Dec { diff := sqrtPriceB.Sub(sqrtPriceA) return liq.Mul(diff) } + +// computeSwapStep calculates the amountIn, amountOut, and the next sqrtPrice given current price, price target, tick liquidity, and amount available to swap +// lte is reference to "less than or equal", which determines if we are moving left or right of the current price to find the next initialized tick with liquidity +func computeSwapStep(sqrtPriceCurrent, sqrtPriceTarget, liquidity, amountRemaining sdk.Dec, lte bool) (sqrtPriceNext sdk.Dec, amountIn sdk.Dec, amountOut sdk.Dec) { + if lte { + sqrtPriceNext = getNextSqrtPriceFromAmount1RoundingDown(sqrtPriceCurrent, liquidity, amountRemaining) + amountIn = calcAmount1Delta(liquidity, sqrtPriceNext, sqrtPriceCurrent) + amountOut = calcAmount0Delta(liquidity, sqrtPriceNext, sqrtPriceCurrent) + } else { + sqrtPriceNext = getNextSqrtPriceFromAmount0RoundingUp(sqrtPriceCurrent, liquidity, amountRemaining) + amountIn = calcAmount0Delta(liquidity, sqrtPriceNext, sqrtPriceCurrent) + amountOut = calcAmount1Delta(liquidity, sqrtPriceNext, sqrtPriceCurrent) + } + return sqrtPriceNext, amountIn, amountOut +} + +func getNextSqrtPriceFromAmount0RoundingUp(sqrtPriceCurrent, liquidity, amountRemaining sdk.Dec) (sqrtPriceNext sdk.Dec) { + numerator := liquidity.Mul(sdk.NewDec(2)) + product := amountRemaining.Mul(sqrtPriceCurrent) + + if product.Quo(amountRemaining).Equal(sqrtPriceCurrent) { + denominator := numerator.Add(product) + if denominator.GTE(numerator) { + numerator = numerator.Mul(sqrtPriceCurrent) + sqrtPriceNext = numerator.QuoRoundUp(denominator) + return sqrtPriceNext + } + } + denominator := numerator.Quo(sqrtPriceCurrent).Add(amountRemaining) + sqrtPriceNext = numerator.QuoRoundUp(denominator) + return sqrtPriceNext +} + +func getNextSqrtPriceFromAmount1RoundingDown(sqrtPriceCurrent, liquidity, amountRemaining sdk.Dec) (sqrtPriceNext sdk.Dec) { + return sqrtPriceCurrent.Add(amountRemaining.Quo(liquidity)) +} diff --git a/x/concentrated-liquidity/pool.go b/x/concentrated-liquidity/pool.go index dae37033186..6ca6a56c292 100644 --- a/x/concentrated-liquidity/pool.go +++ b/x/concentrated-liquidity/pool.go @@ -6,29 +6,30 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/osmosis-labs/osmosis/v12/osmomath" + cltypes "github.com/osmosis-labs/osmosis/v12/x/concentrated-liquidity/types" "github.com/osmosis-labs/osmosis/v12/x/gamm/types" ) -func (k Keeper) CreateNewConcentratedLiquidityPool(ctx sdk.Context, poolId uint64, denom0, denom1 string, currSqrtPrice, currTick sdk.Int) (Pool, error) { +func (k Keeper) CreateNewConcentratedLiquidityPool(ctx sdk.Context, poolId uint64, denom0, denom1 string, currSqrtPrice sdk.Dec, currTick sdk.Int) (Pool, error) { poolAddr := types.NewPoolAddress(poolId) + denom0, denom1, err := cltypes.OrderInitialPoolDenoms(denom0, denom1) + if err != nil { + return Pool{}, err + } pool := Pool{ Address: poolAddr.String(), Id: poolId, CurrentSqrtPrice: currSqrtPrice, CurrentTick: currTick, + Token0: denom0, + Token1: denom1, } k.setPoolById(ctx, poolId, pool) - err := pool.orderInitialPoolDenoms(denom0, denom1) - if err != nil { - return Pool{}, err - } return pool, nil } -// TODO: remove nolint -// nolint: unused func priceToTick(price sdk.Dec) sdk.Int { logOfPrice := osmomath.BigDecFromSDKDec(price).ApproxLog2() logInt := osmomath.NewDecWithPrec(10001, 4) @@ -36,13 +37,6 @@ func priceToTick(price sdk.Dec) sdk.Int { return tick.SDKDec().TruncateInt() } -// TODO: remove nolint -// nolint: unused -func tickToPrice(tick sdk.Int) sdk.Dec { - price := sdk.NewDecWithPrec(10001, 4).Power(tick.Uint64()) - return price -} - func (p Pool) GetAddress() sdk.AccAddress { return sdk.AccAddress{} } @@ -75,128 +69,186 @@ func (p Pool) SpotPrice(ctx sdk.Context, baseAssetDenom string, quoteAssetDenom return sdk.Dec{}, nil } +type SwapState struct { + amountSpecifiedRemaining sdk.Dec // remaining amount of tokens that need to be bought by the pool + amountCalculated sdk.Dec // amount out + sqrtPrice sdk.Dec // new current price when swap is done + tick sdk.Int // new tick when swap is done +} + // this only works on a single directional trade, will implement bi directional trade in next milestone -func (p Pool) CalcOutAmtGivenIn(ctx sdk.Context, tokenIn sdk.Coin, tokenOutDenom string, swapFee sdk.Dec) (tokenOut sdk.Coin, err error) { +func (k Keeper) CalcOutAmtGivenIn(ctx sdk.Context, tokenIn sdk.Coin, tokenOutDenom string, swapFee sdk.Dec, minPrice, maxPrice sdk.Dec, poolId uint64) (newTokenIn, tokenOut sdk.Coin, err error) { + p := k.getPoolbyId(ctx, poolId) asset0 := p.Token0 asset1 := p.Token1 + tokenAmountInAfterFee := tokenIn.Amount.ToDec().Mul(sdk.OneDec().Sub(swapFee)) + + // get current sqrt price from pool + curSqrtPrice := p.CurrentSqrtPrice + + // validation if tokenIn.Denom != asset0 && tokenIn.Denom != asset1 { - return sdk.Coin{}, fmt.Errorf("tokenIn (%s) does not match any asset in pool", tokenIn.Denom) + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("tokenIn (%s) does not match any asset in pool", tokenIn.Denom) } if tokenOutDenom != asset0 && tokenOutDenom != asset1 { - return sdk.Coin{}, fmt.Errorf("tokenOutDenom (%s) does not match any asset in pool", tokenOutDenom) + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("tokenOutDenom (%s) does not match any asset in pool", tokenOutDenom) } if tokenIn.Denom == tokenOutDenom { - return sdk.Coin{}, fmt.Errorf("tokenIn (%s) cannot be the same as tokenOut (%s)", tokenIn.Denom, tokenOutDenom) + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("tokenIn (%s) cannot be the same as tokenOut (%s)", tokenIn.Denom, tokenOutDenom) + } + if minPrice.GTE(curSqrtPrice.Power(2)) { + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("minPrice (%s) must be less than current price (%s)", minPrice, curSqrtPrice.Power(2)) + } + if maxPrice.LTE(curSqrtPrice.Power(2)) { + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("maxPrice (%s) must be greater than current price (%s)", maxPrice, curSqrtPrice.Power(2)) } - tokenAmountInAfterFee := tokenIn.Amount.ToDec().Mul(sdk.OneDec().Sub(swapFee)) - // TODO: Replace with spot price - priceLower := sdk.NewDec(4500) - priceCur := sdk.NewDec(5000) - priceUpper := sdk.NewDec(5500) + // sqrtPrice of upper and lower user defined price range + sqrtPLowerTick, err := minPrice.ApproxSqrt() + if err != nil { + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("issue calculating square root of minPrice") + } - sqrtPLowerTick, _ := priceLower.ApproxSqrt() - sqrtPCurTick, _ := priceCur.ApproxSqrt() - sqrtPUpperTick, _ := priceUpper.ApproxSqrt() + sqrtPUpperTick, err := maxPrice.ApproxSqrt() + if err != nil { + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("issue calculating square root of maxPrice") + } - // TODO: Roman change out with query to pool to get this info + // TODO: How do we remove/generalize this? I am stumped. + // the following coins represent the virtual amounts in the current price bucket amountETH := int64(1000000) amountUSDC := int64(5000000000) - liq0 := liquidity0(amountETH, sqrtPCurTick, sqrtPUpperTick) - liq1 := liquidity1(amountUSDC, sqrtPCurTick, sqrtPLowerTick) + // find liquidity of assetA and assetB + liq0 := liquidity0(amountETH, curSqrtPrice, sqrtPUpperTick) + liq1 := liquidity1(amountUSDC, curSqrtPrice, sqrtPLowerTick) + // utilize the smaller liquidity between assetA and assetB when performing the swap calculation liq := sdk.MinDec(liq0, liq1) - if tokenIn.Denom == asset1 { - priceDiff := tokenAmountInAfterFee.Quo(liq) - priceNext := sqrtPCurTick.Add(priceDiff) - // new amount in, will be needed later - //amountIn = calcAmount1(liq, priceNext, sqrtPCurTick) - amountOut := calcAmount0Delta(liq, priceNext, sqrtPCurTick) - return sdk.NewCoin(tokenOutDenom, amountOut.TruncateInt()), nil - } else if tokenIn.Denom == asset0 { - priceNextTop := liq.Mul(sqrtPCurTick) - priceNextBot := liq.Add(tokenAmountInAfterFee.Mul(sqrtPCurTick)) - priceNext := priceNextTop.Quo(priceNextBot) - // new amount in, will be needed later - //amountIn = calcAmount1(liq, priceNext, sqrtPCurTick) - amountOut := calcAmount1Delta(liq, priceNext, sqrtPCurTick) - return sdk.NewCoin(tokenOutDenom, amountOut.TruncateInt()), nil - } - return sdk.Coin{}, fmt.Errorf("tokenIn (%s) does not match any asset in pool", tokenIn.Denom) + + swapState := SwapState{ + amountSpecifiedRemaining: tokenAmountInAfterFee, + amountCalculated: sdk.ZeroDec(), + sqrtPrice: curSqrtPrice, + tick: priceToTick(curSqrtPrice.Power(2)), + } + + // TODO: This should be GT 0 but some instances have very small remainder + // need to look into fixing this + for swapState.amountSpecifiedRemaining.GT(sdk.NewDecWithPrec(1, 6)) { + lte := tokenIn.Denom == asset1 + nextTick, _ := k.NextInitializedTick(ctx, poolId, swapState.tick.Int64(), lte) + // TODO: we can enable this error checking once we fix tick initialization + // if !ok { + // return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("there are no more ticks initialized to fill the swap") + // } + nextSqrtPrice, err := k.tickToSqrtPrice(sdk.NewInt(nextTick)) + if err != nil { + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("could not convert next tick (%v) to nextSqrtPrice", sdk.NewInt(nextTick)) + } + + sqrtPrice, amountIn, amountOut := computeSwapStep( + swapState.sqrtPrice, + nextSqrtPrice, + liq, + swapState.amountSpecifiedRemaining, + lte, + ) + swapState.amountSpecifiedRemaining = swapState.amountSpecifiedRemaining.Sub(amountIn) + swapState.amountCalculated = swapState.amountCalculated.Add(amountOut) + swapState.tick = priceToTick(sqrtPrice.Power(2)) + } + + newTokenIn.Amount = tokenIn.Amount.Sub(swapState.amountSpecifiedRemaining.RoundInt()) + return sdk.NewCoin(tokenIn.Denom, newTokenIn.Amount), sdk.NewCoin(tokenOutDenom, swapState.amountCalculated.RoundInt()), nil } func (p Pool) SwapInAmtGivenOut(ctx sdk.Context, tokenOut sdk.Coins, tokenInDenom string, swapFee sdk.Dec) (tokenIn sdk.Coin, err error) { return sdk.Coin{}, nil } -func (p Pool) CalcInAmtGivenOut(ctx sdk.Context, tokenOut sdk.Coin, tokenInDenom string, swapFee sdk.Dec) (tokenIn sdk.Coin, err error) { +func (k Keeper) CalcInAmtGivenOut(ctx sdk.Context, tokenOut sdk.Coin, tokenInDenom string, swapFee sdk.Dec, minPrice, maxPrice sdk.Dec, poolId uint64) (tokenIn, newTokenOut sdk.Coin, err error) { tokenOutAmt := tokenOut.Amount.ToDec() + p := k.getPoolbyId(ctx, poolId) asset0 := p.Token0 asset1 := p.Token1 + + // get current sqrt price from pool + // curSqrtPrice := sdk.NewDecWithPrec(int64(p.CurrentSqrtPrice.Uint64()), 6) + curSqrtPrice := p.CurrentSqrtPrice + + // validation if tokenOut.Denom != asset0 && tokenOut.Denom != asset1 { - return sdk.Coin{}, fmt.Errorf("tokenOut denom (%s) does not match any asset in pool", tokenOut.Denom) + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("tokenOut denom (%s) does not match any asset in pool", tokenOut.Denom) } if tokenInDenom != asset0 && tokenInDenom != asset1 { - return sdk.Coin{}, fmt.Errorf("tokenInDenom (%s) does not match any asset in pool", tokenInDenom) + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("tokenInDenom (%s) does not match any asset in pool", tokenInDenom) } if tokenOut.Denom == tokenInDenom { - return sdk.Coin{}, fmt.Errorf("tokenOut (%s) cannot be the same as tokenIn (%s)", tokenOut.Denom, tokenInDenom) + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("tokenOut (%s) cannot be the same as tokenIn (%s)", tokenOut.Denom, tokenInDenom) + } + if minPrice.GTE(curSqrtPrice.Power(2)) { + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("minPrice (%s) must be less than current price (%s)", minPrice, curSqrtPrice.Power(2)) + } + if maxPrice.LTE(curSqrtPrice.Power(2)) { + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("maxPrice (%s) must be greater than current price (%s)", maxPrice, curSqrtPrice.Power(2)) } - // TODO: Replace with spot price - priceLower := sdk.NewDec(4500) - priceCur := sdk.NewDec(5000) - priceUpper := sdk.NewDec(5500) + // sqrtPrice of upper and lower user defined price range + sqrtPLowerTick, err := minPrice.ApproxSqrt() + if err != nil { + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("issue calculating square root of minPrice") + } - sqrtPLowerTick, _ := priceLower.ApproxSqrt() - sqrtPCurTick, _ := priceCur.ApproxSqrt() - sqrtPUpperTick, _ := priceUpper.ApproxSqrt() + sqrtPUpperTick, err := maxPrice.ApproxSqrt() + if err != nil { + return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("issue calculating square root of maxPrice") + } - // TODO: Roman change out with query to pool to get this info + // TODO: How do we remove/generalize this? I am stumped. amountETH := int64(1000000) amountUSDC := int64(5000000000) - liq0 := liquidity0(amountETH, sqrtPCurTick, sqrtPUpperTick) - liq1 := liquidity1(amountUSDC, sqrtPCurTick, sqrtPLowerTick) + // find liquidity of assetA and assetB + liq0 := liquidity0(amountETH, curSqrtPrice, sqrtPUpperTick) + liq1 := liquidity1(amountUSDC, curSqrtPrice, sqrtPLowerTick) + // utilize the smaller liquidity between assetA and assetB when performing the swap calculation liq := sdk.MinDec(liq0, liq1) - if tokenOut.Denom == asset1 { - priceDiff := tokenOutAmt.Quo(liq) - priceNext := sqrtPCurTick.Add(priceDiff) - - // new amount in, will be needed later - // amountIn = calcAmount1(liq, priceNext, sqrtPCurTick) - amountIn := calcAmount0Delta(liq, priceNext, sqrtPCurTick) - // fee logic - amountIn = amountIn.Quo(sdk.OneDec().Sub(swapFee)) - return sdk.NewCoin(tokenInDenom, amountIn.RoundInt()), nil - } else if tokenOut.Denom == asset0 { - priceNextTop := liq.Mul(sqrtPCurTick) - priceNextBot := liq.Add(tokenOutAmt.Mul(sqrtPCurTick)) - priceNext := priceNextTop.Quo(priceNextBot) - - // new amount in, will be needed later - // amountIn = calcAmount1(liq, priceNext, sqrtPCurTick) - amountIn := calcAmount1Delta(liq, priceNext, sqrtPCurTick) - // fee logic - amountIn = amountIn.Quo(sdk.OneDec().Sub(swapFee)) - return sdk.NewCoin(tokenInDenom, amountIn.RoundInt()), nil - } - return sdk.Coin{}, fmt.Errorf("tokenOut (%s) does not match any asset in pool", tokenOut.Denom) -} - -func (p *Pool) orderInitialPoolDenoms(denom0, denom1 string) error { - if denom0 == denom1 { - return fmt.Errorf("cannot have the same asset in a single pool") - } - if denom0 > denom1 { - denom1, denom0 = denom0, denom1 + swapState := SwapState{ + amountSpecifiedRemaining: tokenOutAmt, + amountCalculated: sdk.ZeroDec(), + sqrtPrice: curSqrtPrice, + tick: priceToTick(curSqrtPrice.Power(2)), } - p.Token0 = denom0 - p.Token1 = denom1 - - return nil + // TODO: This should be GT 0 but some instances have very small remainder + // need to look into fixing this + for swapState.amountSpecifiedRemaining.GT(sdk.NewDecWithPrec(1, 6)) { + lte := tokenOut.Denom == asset1 + nextTick, _ := k.NextInitializedTick(ctx, poolId, swapState.tick.Int64(), lte) + // TODO: we can enable this error checking once we fix tick initialization + // if !ok { + // return sdk.Coin{}, sdk.Coin{}, fmt.Errorf("there are no more ticks initialized to fill the swap") + // } + nextSqrtPrice, err := k.tickToSqrtPrice(sdk.NewInt(nextTick)) + if err != nil { + return sdk.Coin{}, sdk.Coin{}, err + } + + // TODO: In and out get flipped based on if we are calculating for in or out, need to fix this + sqrtPrice, amountIn, amountOut := computeSwapStep( + swapState.sqrtPrice, + nextSqrtPrice, + liq, + swapState.amountSpecifiedRemaining, + lte, + ) + + swapState.amountSpecifiedRemaining = swapState.amountSpecifiedRemaining.Sub(amountIn) + swapState.amountCalculated = swapState.amountCalculated.Add(amountOut.Quo(sdk.OneDec().Sub(swapFee))) + swapState.tick = priceToTick(sqrtPrice.Power(2)) + } + return sdk.NewCoin(tokenInDenom, swapState.amountCalculated.RoundInt()), sdk.NewCoin(tokenOut.Denom, tokenOut.Amount), nil } diff --git a/x/concentrated-liquidity/pool_test.go b/x/concentrated-liquidity/pool_test.go index 861aee73068..1d30c495f8f 100644 --- a/x/concentrated-liquidity/pool_test.go +++ b/x/concentrated-liquidity/pool_test.go @@ -1,94 +1,102 @@ package concentrated_liquidity_test import ( - fmt "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" + + cltypes "github.com/osmosis-labs/osmosis/v12/x/concentrated-liquidity/types" ) func (s *KeeperTestSuite) TestCalcOutAmtGivenIn() { ctx := s.Ctx - pool, err := s.App.ConcentratedLiquidityKeeper.CreateNewConcentratedLiquidityPool(s.Ctx, 1, "eth", "usdc", sdk.NewInt(0), sdk.NewInt(0)) + pool, err := s.App.ConcentratedLiquidityKeeper.CreateNewConcentratedLiquidityPool(ctx, 1, "eth", "usdc", sdk.MustNewDecFromStr("70.710678"), sdk.NewInt(85176)) s.Require().NoError(err) // test asset a to b logic tokenIn := sdk.NewCoin("eth", sdk.NewInt(133700)) tokenOutDenom := "usdc" swapFee := sdk.NewDec(0) + minPrice := sdk.NewDec(4500) + maxPrice := sdk.NewDec(5500) - amountOut, err := pool.CalcOutAmtGivenIn(ctx, tokenIn, tokenOutDenom, swapFee) + _, amountOut, err := s.App.ConcentratedLiquidityKeeper.CalcOutAmtGivenIn(ctx, tokenIn, tokenOutDenom, swapFee, minPrice, maxPrice, pool.Id) s.Require().NoError(err) - s.Require().Equal(sdk.NewDec(663944647).String(), amountOut.Amount.ToDec().String()) + s.Require().Equal(sdk.NewDec(666975610).String(), amountOut.Amount.ToDec().String()) // test asset b to a logic tokenIn = sdk.NewCoin("usdc", sdk.NewInt(4199999999)) tokenOutDenom = "eth" swapFee = sdk.NewDec(0) - amountOut, err = pool.CalcOutAmtGivenIn(ctx, tokenIn, tokenOutDenom, swapFee) + _, amountOut, err = s.App.ConcentratedLiquidityKeeper.CalcOutAmtGivenIn(ctx, tokenIn, tokenOutDenom, swapFee, minPrice, maxPrice, pool.Id) s.Require().NoError(err) s.Require().Equal(sdk.NewDec(805287), amountOut.Amount.ToDec()) + // test asset b to a logic + tokenIn = sdk.NewCoin("usdc", sdk.NewInt(42000000)) + tokenOutDenom = "eth" + swapFee = sdk.NewDec(0) + + _, amountOut, err = s.App.ConcentratedLiquidityKeeper.CalcOutAmtGivenIn(ctx, tokenIn, tokenOutDenom, swapFee, minPrice, maxPrice, pool.Id) + s.Require().NoError(err) + s.Require().Equal(sdk.NewDec(8396), amountOut.Amount.ToDec()) + // test with swap fee tokenIn = sdk.NewCoin("usdc", sdk.NewInt(4199999999)) tokenOutDenom = "eth" swapFee = sdk.NewDecWithPrec(2, 2) - amountOut, err = pool.CalcOutAmtGivenIn(ctx, tokenIn, tokenOutDenom, swapFee) + _, amountOut, err = s.App.ConcentratedLiquidityKeeper.CalcOutAmtGivenIn(ctx, tokenIn, tokenOutDenom, swapFee, minPrice, maxPrice, pool.Id) s.Require().NoError(err) s.Require().Equal(sdk.NewDec(789834), amountOut.Amount.ToDec()) } func (s *KeeperTestSuite) TestCalcInAmtGivenOut() { ctx := s.Ctx - fmt.Println("===0") - pool, err := s.App.ConcentratedLiquidityKeeper.CreateNewConcentratedLiquidityPool(s.Ctx, 1, "eth", "usdc", sdk.NewInt(0), sdk.NewInt(0)) + pool, err := s.App.ConcentratedLiquidityKeeper.CreateNewConcentratedLiquidityPool(s.Ctx, 1, "eth", "usdc", sdk.MustNewDecFromStr("70.710678"), sdk.NewInt(85176)) s.Require().NoError(err) + // test asset a to b logic - tokensOut := sdk.NewCoin("usdc", sdk.NewInt(4199999999)) + tokenOut := sdk.NewCoin("usdc", sdk.NewInt(4199999999)) tokenInDenom := "eth" swapFee := sdk.NewDec(0) + minPrice := sdk.NewDec(4500) + maxPrice := sdk.NewDec(5500) - amountIn, err := pool.CalcInAmtGivenOut(ctx, tokensOut, tokenInDenom, swapFee) + amountIn, _, err := s.App.ConcentratedLiquidityKeeper.CalcInAmtGivenOut(ctx, tokenOut, tokenInDenom, swapFee, minPrice, maxPrice, pool.Id) s.Require().NoError(err) s.Require().Equal(sdk.NewDec(805287), amountIn.Amount.ToDec()) // test asset b to a logic - tokensOut = sdk.NewCoin("eth", sdk.NewInt(133700)) + tokenOut = sdk.NewCoin("eth", sdk.NewInt(133700)) tokenInDenom = "usdc" swapFee = sdk.NewDec(0) - amountIn, err = pool.CalcInAmtGivenOut(ctx, tokensOut, tokenInDenom, swapFee) + amountIn, _, err = s.App.ConcentratedLiquidityKeeper.CalcInAmtGivenOut(ctx, tokenOut, tokenInDenom, swapFee, minPrice, maxPrice, pool.Id) s.Require().NoError(err) - s.Require().Equal(sdk.NewDec(663944647), amountIn.Amount.ToDec()) + s.Require().Equal(sdk.NewDec(666975610), amountIn.Amount.ToDec()) // test asset a to b logic - tokensOut = sdk.NewCoin("usdc", sdk.NewInt(4199999999)) + tokenOut = sdk.NewCoin("usdc", sdk.NewInt(4199999999)) tokenInDenom = "eth" swapFee = sdk.NewDecWithPrec(2, 2) - amountIn, err = pool.CalcInAmtGivenOut(ctx, tokensOut, tokenInDenom, swapFee) + amountIn, _, err = s.App.ConcentratedLiquidityKeeper.CalcInAmtGivenOut(ctx, tokenOut, tokenInDenom, swapFee, minPrice, maxPrice, pool.Id) s.Require().NoError(err) s.Require().Equal(sdk.NewDec(821722), amountIn.Amount.ToDec()) } func (s *KeeperTestSuite) TestOrderInitialPoolDenoms() { - pool, err := s.App.ConcentratedLiquidityKeeper.CreateNewConcentratedLiquidityPool(s.Ctx, 1, "eth", "usdc", sdk.NewInt(0), sdk.NewInt(0)) - s.Require().NoError(err) - s.Require().Equal(pool.Token0, "eth") - s.Require().Equal(pool.Token1, "usdc") - - err = pool.OrderInitialPoolDenoms("axel", "osmo") + denom0, denom1, err := cltypes.OrderInitialPoolDenoms("axel", "osmo") s.Require().NoError(err) - s.Require().Equal(pool.Token0, "axel") - s.Require().Equal(pool.Token1, "osmo") + s.Require().Equal(denom0, "axel") + s.Require().Equal(denom1, "osmo") - err = pool.OrderInitialPoolDenoms("usdc", "eth") + denom0, denom1, err = cltypes.OrderInitialPoolDenoms("usdc", "eth") s.Require().NoError(err) - s.Require().Equal(pool.Token0, "eth") - s.Require().Equal(pool.Token1, "usdc") + s.Require().Equal(denom0, "eth") + s.Require().Equal(denom1, "usdc") - err = pool.OrderInitialPoolDenoms("usdc", "usdc") + denom0, denom1, err = cltypes.OrderInitialPoolDenoms("usdc", "usdc") s.Require().Error(err) } diff --git a/x/concentrated-liquidity/tick.go b/x/concentrated-liquidity/tick.go index 65d5366786b..5b38be7b4e4 100644 --- a/x/concentrated-liquidity/tick.go +++ b/x/concentrated-liquidity/tick.go @@ -12,13 +12,13 @@ import ( types "github.com/osmosis-labs/osmosis/v12/x/concentrated-liquidity/types" ) -func (k Keeper) getSqrtRatioAtTick(tickIndex int64) (sdk.Dec, error) { - sqrtRatio, err := sdk.NewDecWithPrec(10001, 4).Power(uint64(tickIndex)).ApproxSqrt() +func (k Keeper) tickToSqrtPrice(tickIndex sdk.Int) (sdk.Dec, error) { + price, err := sdk.NewDecWithPrec(10001, 4).Power(tickIndex.Uint64()).ApproxSqrt() if err != nil { - return sdk.Dec{}, nil + return sdk.Dec{}, err } - return sqrtRatio, nil + return price, nil } // TODO: implement this diff --git a/x/concentrated-liquidity/types/cl.go b/x/concentrated-liquidity/types/cl.go new file mode 100644 index 00000000000..1c38958eca6 --- /dev/null +++ b/x/concentrated-liquidity/types/cl.go @@ -0,0 +1,14 @@ +package types + +import fmt "fmt" + +func OrderInitialPoolDenoms(denom0, denom1 string) (string, string, error) { + if denom0 == denom1 { + return "", "", fmt.Errorf("cannot have the same asset in a single pool") + } + if denom0 > denom1 { + denom1, denom0 = denom0, denom1 + } + + return denom0, denom1, nil +}