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

JoinPoolNoSwap simulation #3242

Merged
merged 18 commits into from
Nov 14, 2022
25 changes: 24 additions & 1 deletion proto/osmosis/gamm/v1beta1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ service Query {
option (google.api.http).get = "/osmosis/gamm/v1beta1/pool_type/{pool_id}";
}

// Simulates joining pool without a swap. Returns the amount of shares you'd
// get and tokens needed to provide
rpc CalcJoinPoolNoSwapShares(QueryCalcJoinPoolNoSwapSharesRequest)
returns (QueryCalcJoinPoolNoSwapSharesResponse) {}

rpc CalcJoinPoolShares(QueryCalcJoinPoolSharesRequest)
returns (QueryCalcJoinPoolSharesResponse) {
option (google.api.http).get =
Expand Down Expand Up @@ -192,7 +197,25 @@ message QueryTotalSharesResponse {
(gogoproto.nullable) = false
];
}

//=============================== CalcJoinPoolNoSwapShares
message QueryCalcJoinPoolNoSwapSharesRequest {
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 QueryCalcJoinPoolNoSwapSharesResponse {
repeated cosmos.base.v1beta1.Coin tokens_out = 1 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.moretags) = "yaml:\"tokens_out\"",
(gogoproto.nullable) = false
];
string shares_out = 2 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
}
// QuerySpotPriceRequest defines the gRPC request structure for a SpotPrice
// query.
message QuerySpotPriceRequest {
Expand Down
23 changes: 23 additions & 0 deletions x/gamm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,29 @@ func (q Querier) CalcExitPoolCoinsFromShares(ctx context.Context, req *types.Que
return &types.QueryCalcExitPoolCoinsFromSharesResponse{TokensOut: exitCoins}, nil
}

// CalcJoinPoolNoSwapShares returns the amount of shares you'd get if joined a pool without a swap and tokens which need to be provided
func (q Querier) CalcJoinPoolNoSwapShares(ctx context.Context, req *types.QueryCalcJoinPoolNoSwapSharesRequest) (*types.QueryCalcJoinPoolNoSwapSharesResponse, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please add this and your previous query to this test:

func (s *QueryTestSuite) TestQueriesNeverAlterState() {

It ensures that queries don't alter state. This would be very useful for a stargate query

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, I'll change this ASAP

if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}

sdkCtx := sdk.UnwrapSDKContext(ctx)
pool, err := q.GetPoolAndPoke(sdkCtx, req.PoolId)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method calls PokePool() which may update pool weights for a balancer pool. Is this acceptable for a stargate query?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

@apollo-sturdy apollo-sturdy Nov 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it's fine: #2972 (comment)

So then this PR should be able to be merged?

@p0mvn

if err != nil {
return nil, err
}

sharesOut, tokensJoined, err := pool.CalcJoinPoolNoSwapShares(sdkCtx, req.TokensIn, pool.GetSwapFee(sdkCtx))
if err != nil {
return nil, err
}

return &types.QueryCalcJoinPoolNoSwapSharesResponse{
TokensOut: tokensJoined,
SharesOut: sharesOut,
}, 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
78 changes: 77 additions & 1 deletion x/gamm/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,83 @@ func (suite *KeeperTestSuite) TestCalcExitPoolCoinsFromShares() {
}
}

func (suite *KeeperTestSuite) TestCalcJoinPoolNoSwapShares() {
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,
},
{
"invalid single asset join test case",
poolId,
sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(1000000))),
errors.New("no-swap joins require LP'ing with all assets in pool"),
},
{
"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("no-swap joins require LP'ing with all assets in pool"),
},
}

for _, tc := range testCases {
suite.Run(tc.name, func() {
out, err := queryClient.CalcJoinPoolNoSwapShares(gocontext.Background(), &types.QueryCalcJoinPoolNoSwapSharesRequest{
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.CalcJoinPoolNoSwapShares(ctx, tc.tokensIn, swapFee)
suite.Require().NoError(err)
suite.Require().Equal(numShares, out.SharesOut)
suite.Require().Equal(numLiquidity, out.TokensOut)
} else {
suite.Require().EqualError(err, tc.expectedErr.Error())
}
})
}
}

func (suite *KeeperTestSuite) TestPoolsWithFilter() {
var (
defaultAcctFunds sdk.Coins = sdk.NewCoins(
Expand Down Expand Up @@ -257,7 +334,6 @@ func (suite *KeeperTestSuite) TestPoolsWithFilter() {
}
})
}

}

func (suite *KeeperTestSuite) TestCalcJoinPoolShares() {
Expand Down
Loading