Skip to content

Commit

Permalink
feat(gamm): CalcJoinPoolShares and CalcExitPoolCoinsFromShares queries (
Browse files Browse the repository at this point in the history
#2972)

* JoinSwapExactAmountIn query

* ExitSwapShareAmountIn query

* JoinSwapExactAmountIn, ExitSwapShareAmountIn queries

* JoinSwapExactAmountIn, ExitSwapShareAmountIn queries

* JoinSwapExactAmountIn, ExitSwapShareAmountIn queries

* JoinSwapExactAmountIn, ExitSwapShareAmountIn queries

* JoinSwapExactAmountIn, ExitSwapShareAmountIn queries

* JoinSwapExactAmountIn, ExitSwapShareAmountIn queries

* save

* test: finish CalcJoinPoolShares query test

* tests for CalcExitPoolCoinsFromShares

* tests for CalcExitPoolCoinsFromShares

* test: add additional exit coin tests

* Update x/gamm/keeper/grpc_query.go

Co-authored-by: Aleksandr Bezobchuk <[email protected]>

* Update x/gamm/keeper/grpc_query.go

Co-authored-by: Aleksandr Bezobchuk <[email protected]>

* Update x/gamm/keeper/grpc_query.go

Co-authored-by: Aleksandr Bezobchuk <[email protected]>

* queries

* docs fix

* chore: generate protobuf files

* feat: remove TokensOutDenoms, fix typos

* Update x/gamm/keeper/grpc_query_test.go

Co-authored-by: Ruslan Akhtariev <[email protected]>
Co-authored-by: George McAskill <[email protected]>
Co-authored-by: Roman <[email protected]>
Co-authored-by: Aleksandr Bezobchuk <[email protected]>
(cherry picked from commit 94534da)

# Conflicts:
#	CHANGELOG.md
  • Loading branch information
pysel authored and mergify[bot] committed Nov 3, 2022
1 parent 8d68e4b commit f9d2dde
Show file tree
Hide file tree
Showing 7 changed files with 1,552 additions and 136 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Features

* [#2739](https://github.com/osmosis-labs/osmosis/pull/2739) Add pool type query
<<<<<<< HEAD
=======
* [#2956](https://github.com/osmosis-labs/osmosis/issues/2956) Add queries for calculating amount of shares/tokens you get by providing X tokens/shares when entering/exiting a pool

### Bug fixes

>>>>>>> 94534da0 (feat(gamm): CalcJoinPoolShares and CalcExitPoolCoinsFromShares queries (#2972))
* [#2803](https://github.com/osmosis-labs/osmosis/pull/2803) Fix total pool liquidity CLI query.
* [#2914](https://github.com/osmosis-labs/osmosis/pull/2914) Remove out of gas panics from node logs

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.18
require (
github.com/CosmWasm/wasmd v0.28.0-osmo-v12
github.com/cosmos/cosmos-proto v1.0.0-alpha8
github.com/cosmos/cosmos-sdk v0.46.1
github.com/cosmos/cosmos-sdk v0.46.4
github.com/cosmos/go-bip39 v1.0.0
github.com/cosmos/iavl v0.19.1
github.com/cosmos/ibc-go/v3 v3.3.0
Expand Down
46 changes: 46 additions & 0 deletions proto/osmosis/gamm/v1beta1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ service Query {
option (google.api.http).get = "/osmosis/gamm/v1beta1/pool_type/{pool_id}";
}

rpc CalcJoinPoolShares(QueryCalcJoinPoolSharesRequest)
returns (QueryCalcJoinPoolSharesResponse) {
option (google.api.http).get =
"/osmosis/gamm/v1beta1/pools/{pool_id}/join_swap_exact_in";
}
rpc CalcExitPoolCoinsFromShares(QueryCalcExitPoolCoinsFromSharesRequest)
returns (QueryCalcExitPoolCoinsFromSharesResponse) {
option (google.api.http).get =
"/osmosis/gamm/v1beta1/pools/{pool_id}/exit_swap_share_amount_in";
}

rpc PoolParams(QueryPoolParamsRequest) returns (QueryPoolParamsResponse) {
option (google.api.http).get =
"/osmosis/gamm/v1beta1/pools/{pool_id}/params";
Expand Down Expand Up @@ -110,6 +121,41 @@ message QueryPoolTypeResponse {
string pool_type = 1 [ (gogoproto.moretags) = "yaml:\"pool_type\"" ];
}

//=============================== CalcJoinPoolShares
message QueryCalcJoinPoolSharesRequest {
uint64 pool_id = 1 [ (gogoproto.moretags) = "yaml:\"pool_id\"" ];
repeated cosmos.base.v1beta1.Coin tokens_in = 2 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false
];
}
message QueryCalcJoinPoolSharesResponse {
string share_out_amount = 1 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.moretags) = "yaml:\"share_out_amount\"",
(gogoproto.nullable) = false
];
repeated cosmos.base.v1beta1.Coin tokens_out = 2 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false
];
}

//=============================== CalcExitPoolCoinsFromShares
message QueryCalcExitPoolCoinsFromSharesRequest {
uint64 pool_id = 1;
string share_in_amount = 2 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
}
message QueryCalcExitPoolCoinsFromSharesResponse {
repeated cosmos.base.v1beta1.Coin tokens_out = 1 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false
];
}

//=============================== PoolParams
message QueryPoolParamsRequest {
uint64 pool_id = 1 [ (gogoproto.moretags) = "yaml:\"pool_id\"" ];
Expand Down
53 changes: 53 additions & 0 deletions x/gamm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,59 @@ func (q Querier) PoolType(ctx context.Context, req *types.QueryPoolTypeRequest)
}, err
}

// CalcJoinPoolShares queries the amount of shares you get by providing specific amount of tokens
func (q Querier) CalcJoinPoolShares(ctx context.Context, req *types.QueryCalcJoinPoolSharesRequest) (*types.QueryCalcJoinPoolSharesResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if req.TokensIn == nil {
return nil, status.Error(codes.InvalidArgument, "no tokens in")
}

sdkCtx := sdk.UnwrapSDKContext(ctx)
pool, err := q.Keeper.getPoolForSwap(sdkCtx, req.PoolId)
if err != nil {
return nil, err
}

numShares, newLiquidity, err := pool.CalcJoinPoolShares(sdkCtx, req.TokensIn, pool.GetSwapFee(sdkCtx))
if err != nil {
return nil, err
}

return &types.QueryCalcJoinPoolSharesResponse{
ShareOutAmount: numShares,
TokensOut: newLiquidity,
}, nil
}

// CalcExitPoolCoinsFromShares queries the amount of tokens you get by exiting a specific amount of shares
func (q Querier) CalcExitPoolCoinsFromShares(ctx context.Context, req *types.QueryCalcExitPoolCoinsFromSharesRequest) (*types.QueryCalcExitPoolCoinsFromSharesResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}

sdkCtx := sdk.UnwrapSDKContext(ctx)
pool, err := q.Keeper.GetPoolAndPoke(sdkCtx, req.PoolId)
if err != nil {
return nil, types.ErrPoolNotFound
}

exitFee := pool.GetExitFee(sdkCtx)

totalSharesAmount := pool.GetTotalShares()
if req.ShareInAmount.GTE(totalSharesAmount) || req.ShareInAmount.LTE(sdk.ZeroInt()) {
return nil, sdkerrors.Wrapf(types.ErrInvalidMathApprox, "share ratio is zero or negative")
}

exitCoins, err := pool.CalcExitPoolCoinsFromShares(sdkCtx, req.ShareInAmount, exitFee)
if err != nil {
return nil, err
}

return &types.QueryCalcExitPoolCoinsFromSharesResponse{TokensOut: exitCoins}, nil
}

// PoolParams queries a specified pool for its params.
func (q Querier) PoolParams(ctx context.Context, req *types.QueryPoolParamsRequest) (*types.QueryPoolParamsResponse, error) {
if req == nil {
Expand Down
154 changes: 154 additions & 0 deletions x/gamm/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,167 @@ package keeper_test

import (
gocontext "context"
"errors"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/query"

"github.com/osmosis-labs/osmosis/v12/x/gamm/types"
)

func (suite *KeeperTestSuite) TestCalcExitPoolCoinsFromShares() {
queryClient := suite.queryClient
ctx := suite.Ctx
poolId := suite.PrepareBalancerPool()
exitFee := sdk.ZeroDec()

testCases := []struct {
name string
poolId uint64
shareInAmount sdk.Int
expectedErr error
}{
{
"valid test case",
poolId,
sdk.NewInt(1000000000000000000),
nil,
},
{
"pool id does not exist",
poolId + 1,
sdk.NewInt(1000000000000000000),
types.ErrPoolNotFound,
},
{
"zero share in amount",
poolId,
sdk.ZeroInt(),
sdkerrors.Wrapf(types.ErrInvalidMathApprox, "share ratio is zero or negative"),
},
{
"negative share in amount",
poolId,
sdk.NewInt(-10000),
sdkerrors.Wrapf(types.ErrInvalidMathApprox, "share ratio is zero or negative"),
},
}

for _, tc := range testCases {
suite.Run(tc.name, func() {
out, err := queryClient.CalcExitPoolCoinsFromShares(gocontext.Background(), &types.QueryCalcExitPoolCoinsFromSharesRequest{
PoolId: tc.poolId,
ShareInAmount: tc.shareInAmount,
})
if tc.expectedErr == nil {
poolRes, err := queryClient.Pool(gocontext.Background(), &types.QueryPoolRequest{
PoolId: tc.poolId,
})
suite.Require().NoError(err)

var pool types.PoolI
err = suite.App.InterfaceRegistry().UnpackAny(poolRes.Pool, &pool)
suite.Require().NoError(err)

exitCoins, err := pool.CalcExitPoolCoinsFromShares(ctx, tc.shareInAmount, exitFee)
suite.Require().NoError(err)

// For each coin in exitCoins we are looking for a match in our response
// We need to find exactly len(out) such matches
coins_checked := 0
for _, coin := range exitCoins {
for _, actual_coin := range out.TokensOut {
if coin.Denom == actual_coin.Denom {
suite.Require().Equal(coin.Amount, actual_coin.Amount)
coins_checked++
}
}
}
suite.Require().Equal(out.TokensOut, exitCoins)
} else {
suite.Require().ErrorIs(err, tc.expectedErr)
}
})
}
}
func (suite *KeeperTestSuite) TestCalcJoinPoolShares() {
queryClient := suite.queryClient
ctx := suite.Ctx
poolId := suite.PrepareBalancerPool()
swapFee := sdk.ZeroDec()

testCases := []struct {
name string
poolId uint64
tokensIn sdk.Coins
expectedErr error
}{
{
"valid uneven multi asset join test case",
poolId,
sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(5000000)), sdk.NewCoin("bar", sdk.NewInt(5000000)), sdk.NewCoin("baz", sdk.NewInt(5000000)), sdk.NewCoin("uosmo", sdk.NewInt(5000000))),
nil,
},
{
"valid even multi asset join test case",
poolId,
sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(500000)), sdk.NewCoin("bar", sdk.NewInt(1000000)), sdk.NewCoin("baz", sdk.NewInt(1500000)), sdk.NewCoin("uosmo", sdk.NewInt(2000000))),
nil,
},
{
"valid single asset join test case",
poolId,
sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(1000000))),
nil,
},
{
"pool id does not exist",
poolId + 1,
sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(1000000))),
types.PoolDoesNotExistError{PoolId: poolId + 1},
},
{
"token in denom does not exist",
poolId,
sdk.NewCoins(sdk.NewCoin("random", sdk.NewInt(10000))),
sdkerrors.Wrapf(types.ErrDenomNotFoundInPool, "input denoms must already exist in the pool (%s)", "random"),
},
{
"join pool with incorrect amount of assets",
poolId,
sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(10000)), sdk.NewCoin("bar", sdk.NewInt(10000))),
errors.New("balancer pool only supports LP'ing with one asset or all assets in pool"),
},
}

for _, tc := range testCases {
suite.Run(tc.name, func() {
out, err := queryClient.CalcJoinPoolShares(gocontext.Background(), &types.QueryCalcJoinPoolSharesRequest{
PoolId: tc.poolId,
TokensIn: tc.tokensIn,
})
if tc.expectedErr == nil {
poolRes, err := queryClient.Pool(gocontext.Background(), &types.QueryPoolRequest{
PoolId: tc.poolId,
})
suite.Require().NoError(err)

var pool types.PoolI
err = suite.App.InterfaceRegistry().UnpackAny(poolRes.Pool, &pool)
suite.Require().NoError(err)

numShares, numLiquidity, err := pool.CalcJoinPoolShares(ctx, tc.tokensIn, swapFee)
suite.Require().NoError(err)
suite.Require().Equal(numShares, out.ShareOutAmount)
suite.Require().Equal(numLiquidity, out.TokensOut)
} else {
suite.Require().EqualError(err, tc.expectedErr.Error())
}
})
}

}
func (suite *KeeperTestSuite) TestQueryPool() {
queryClient := suite.queryClient

Expand Down
Loading

0 comments on commit f9d2dde

Please sign in to comment.