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

Refactor: Change Pool API to have separate methods for JoinPool and JoinPoolNoSwap #2133

Merged
merged 31 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9f86b96
split out no swap joins on pool interface and implement changes for b…
AlpinYukseloglu Jul 19, 2022
3cb4b3b
minor refactor and comment changes for clarity
AlpinYukseloglu Jul 19, 2022
654fdf4
minor comment changes for review clarity
AlpinYukseloglu Jul 19, 2022
019e3e5
add tests for new calcs
AlpinYukseloglu Jul 19, 2022
1fdf342
Merge branch 'main' into separate-join-pool
AlpinYukseloglu Jul 19, 2022
5633945
fix merge-related issues
AlpinYukseloglu Jul 19, 2022
f1a00eb
add changelog entry
AlpinYukseloglu Jul 19, 2022
08d2b8e
add failure case test and make existing tests more robust
AlpinYukseloglu Jul 20, 2022
eebb7cc
Merge branch 'separate-join-pool' of https://github.com/osmosis-labs/…
AlpinYukseloglu Jul 20, 2022
43772e7
Merge branch 'main' into separate-join-pool
AlpinYukseloglu Jul 20, 2022
79e4232
clean up validation logic
AlpinYukseloglu Jul 20, 2022
82ff209
Merge branch 'separate-join-pool' of https://github.com/osmosis-labs/…
AlpinYukseloglu Jul 20, 2022
a251f7c
Merge branch 'main' into separate-join-pool
czarcas7ic Jul 21, 2022
a370c6e
factor out common denom check and add tests for it
AlpinYukseloglu Aug 8, 2022
dcbb25a
add additional test cases
AlpinYukseloglu Aug 8, 2022
d9fe736
apply new internal fx where relevant and ensure proper testing
AlpinYukseloglu Aug 8, 2022
8806506
Merge branch 'main' into separate-join-pool
AlpinYukseloglu Aug 10, 2022
1a1509c
add test case and improve test readability
AlpinYukseloglu Aug 12, 2022
20f068e
Merge branch 'main' into separate-join-pool
AlpinYukseloglu Aug 13, 2022
4797f8f
add tests for edge cases and clean up existing tests
AlpinYukseloglu Aug 31, 2022
eda585c
add duplicate pool edge case test
AlpinYukseloglu Aug 31, 2022
0e5f5fa
minor comment updates from code review
AlpinYukseloglu Aug 31, 2022
1eae187
remove remainingCoins return value from internal no-swap calcs
AlpinYukseloglu Sep 1, 2022
8b6f485
update mocks
AlpinYukseloglu Sep 5, 2022
6147129
Merge branch 'main' into separate-join-pool
AlpinYukseloglu Sep 5, 2022
0e55e9d
move ensuredenominpool test to amm tests
AlpinYukseloglu Sep 7, 2022
005ae6a
minor comment updates from code review
AlpinYukseloglu Sep 7, 2022
096d5b2
Merge branch 'separate-join-pool' of https://github.com/osmosis-labs/…
AlpinYukseloglu Sep 7, 2022
2f53651
pass ctx through no-swap pool joins
AlpinYukseloglu Sep 7, 2022
d91e0b5
add multi asset and edge case tests to calcnoswap
AlpinYukseloglu Sep 7, 2022
0ef8a99
Merge branch 'main' into separate-join-pool
AlpinYukseloglu Sep 7, 2022
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#1667](https://github.com/osmosis-labs/osmosis/pull/1673) Move wasm-bindings code out of app package into its own root level package.
* [#2013](https://github.com/osmosis-labs/osmosis/pull/2013) Make `SetParams`, `SetPool`, `SetTotalLiquidity`, and `SetDenomLiquidity` GAMM APIs private
* [#1857](https://github.com/osmosis-labs/osmosis/pull/1857) x/mint rename GetLastHalvenEpochNum to GetLastReductionEpochNum
* [#2133](https://github.com/osmosis-labs/osmosis/pull/2133) Add `JoinPoolNoSwap` and `CalcJoinPoolNoSwapShares` to GAMM pool interface and route `JoinPoolNoSwap` in pool_service.go to new method in pool interface
* [#2394](https://github.com/osmosis-labs/osmosis/pull/2394) Remove unused interface methods from expected keepers of each module
* [#2390](https://github.com/osmosis-labs/osmosis/pull/2390) x/mint remove unused mintCoins parameter from AfterDistributeMintedCoin
* [#2418](https://github.com/osmosis-labs/osmosis/pull/2418) x/mint remove SetInitialSupplyOffsetDuringMigration from keeper
Expand Down
62 changes: 62 additions & 0 deletions tests/mocks/mock_pool.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion x/gamm/keeper/pool_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ func (k Keeper) JoinPoolNoSwap(
}
}

sharesOut, err = pool.JoinPool(ctx, neededLpLiquidity, pool.GetSwapFee(ctx))
sharesOut, err = pool.JoinPoolNoSwap(ctx, neededLpLiquidity, pool.GetSwapFee(ctx))
if err != nil {
return nil, sdk.ZeroInt(), err
}
Expand Down
12 changes: 12 additions & 0 deletions x/gamm/pool-models/balancer/amm.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,15 @@ func calcPoolSharesInGivenSingleAssetOut(
sharesInFeeIncluded := sharesIn.Quo(sdk.OneDec().Sub(exitFee))
return sharesInFeeIncluded
}

// check to make sure the input denoms exist in the provided pool asset map
AlpinYukseloglu marked this conversation as resolved.
Show resolved Hide resolved
func ensureDenomInPool(poolAssetsByDenom map[string]PoolAsset, tokensIn sdk.Coins) error {
AlpinYukseloglu marked this conversation as resolved.
Show resolved Hide resolved
for _, coin := range tokensIn {
_, ok := poolAssetsByDenom[coin.Denom]
if !ok {
return sdkerrors.Wrapf(types.ErrDenomNotFoundInPool, invalidInputDenomsErrFormat, coin.Denom)
}
}

return nil
}
1 change: 1 addition & 0 deletions x/gamm/pool-models/balancer/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var (
UpdateIntermediaryPoolAssetsLiquidity = updateIntermediaryPoolAssetsLiquidity

GetPoolAssetsByDenom = getPoolAssetsByDenom
EnsureDenomInPool = ensureDenomInPool
)

func (p *Pool) CalcSingleAssetJoin(tokenIn sdk.Coin, swapFee sdk.Dec, tokenInPoolAsset PoolAsset, totalShares sdk.Int) (numShares sdk.Int, err error) {
Expand Down
77 changes: 67 additions & 10 deletions x/gamm/pool-models/balancer/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,19 @@ func (p *Pool) JoinPool(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (n
return numShares, nil
}

// JoinPoolNoSwap calculates the number of shares needed for an all-asset join given tokensIn with swapFee applied.
// It updates the liquidity if the pool is joined successfully. If not, returns error.
func (p *Pool) JoinPoolNoSwap(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, err error) {
AlpinYukseloglu marked this conversation as resolved.
Show resolved Hide resolved
numShares, tokensJoined, err := p.CalcJoinPoolNoSwapShares(ctx, tokensIn, swapFee)
if err != nil {
return sdk.Int{}, err
}

// update pool with the calculated share and liquidity needed to join pool
p.IncreaseLiquidity(numShares, tokensJoined)
return numShares, nil
}

// CalcJoinPoolShares calculates the number of shares created to join pool with the provided amount of `tokenIn`.
Copy link
Member

Choose a reason for hiding this comment

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

So now this method is basically not called in the codebase anywhere, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

CalcJoinPoolShares? Do you mean calls outside of the module? Otherwise it's called in JoinPool

Copy link
Member

Choose a reason for hiding this comment

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

But JoinPool is no longer called in the state machien right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I might be missing your point here but I believe JoinPool is still called in JoinSwapExactAmountIn:

sharesOut, err := pool.JoinPool(ctx, tokensIn, pool.GetSwapFee(ctx))

// The input tokens must either be:
// - a single token
Expand All @@ -687,12 +700,10 @@ func (p *Pool) CalcJoinPoolShares(ctx sdk.Context, tokensIn sdk.Coins, swapFee s
return sdk.ZeroInt(), sdk.NewCoins(), err
}

// check to make sure the input denoms exist in the pool
for _, coin := range tokensIn {
_, ok := poolAssetsByDenom[coin.Denom]
if !ok {
return sdk.ZeroInt(), sdk.NewCoins(), sdkerrors.Wrapf(types.ErrDenomAlreadyInPool, invalidInputDenomsErrFormat, coin.Denom)
}
// check to make sure the input denom exists in the pool
err = ensureDenomInPool(poolAssetsByDenom, tokensIn)
if err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
}

totalShares := p.GetTotalShares()
Expand All @@ -713,12 +724,15 @@ func (p *Pool) CalcJoinPoolShares(ctx sdk.Context, tokensIn sdk.Coins, swapFee s
// * numShares is how many shares are perfectly matched.
// * remainingTokensIn is how many coins we have left to join, that have not already been used.
// if remaining coins is empty, logic is done (we joined all tokensIn)
numShares, remainingTokensIn, err := cfmm_common.MaximalExactRatioJoin(p, sdk.Context{}, tokensIn)
numShares, tokensJoined, err = p.CalcJoinPoolNoSwapShares(ctx, tokensIn, swapFee)
if err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
}
if remainingTokensIn.Empty() {
tokensJoined = tokensIn

// safely ends the calculation if all input tokens are successfully LP'd
if tokensJoined.IsAnyGT(tokensIn) {
return sdk.ZeroInt(), sdk.NewCoins(), errors.New("an error has occurred, more coins joined than tokens passed in")
AlpinYukseloglu marked this conversation as resolved.
Show resolved Hide resolved
} else if tokensJoined.IsEqual(tokensIn) {
return numShares, tokensJoined, nil
}

Expand All @@ -727,13 +741,13 @@ func (p *Pool) CalcJoinPoolShares(ctx sdk.Context, tokensIn sdk.Coins, swapFee s
// Instead, it mutates the pool assets argument to be further used by the caller.
// * We add the joined coins to our "current pool liquidity" object (poolAssetsByDenom)
// * We increment a variable for our "newTotalShares" to add in the shares that've been added.
tokensJoined = tokensIn.Sub(remainingTokensIn)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Context: CalcJoinPoolNoSwapShares includes this calculation and returns the resulting tokensJoined above

if err := updateIntermediaryPoolAssetsLiquidity(tokensJoined, poolAssetsByDenom); err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
}
newTotalShares := totalShares.Add(numShares)

// 5) Now single asset join each remaining coin.
remainingTokensIn := tokensIn.Sub(tokensJoined)
newNumSharesFromRemaining, newLiquidityFromRemaining, err := p.calcJoinSingleAssetTokensIn(remainingTokensIn, newTotalShares, poolAssetsByDenom, swapFee)
if err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
Expand All @@ -749,6 +763,49 @@ func (p *Pool) CalcJoinPoolShares(ctx sdk.Context, tokensIn sdk.Coins, swapFee s
return numShares, tokensJoined, nil
}

// CalcJoinPoolNoSwapShares calculates the number of shares created to execute an all-asset pool join with the provided amount of `tokensIn`.
// The input tokens must contain exactly the same tokens as the pool contains
AlpinYukseloglu marked this conversation as resolved.
Show resolved Hide resolved
//
// Returns the number of shares created, the amount of coins actually joined into the pool, (in case of not being able to fully join),
// and the remaining tokens in `tokensIn` after joining. If an all-asset join is not possible, returns an error.
//
// Since CalcJoinPoolNoSwapShares is non-mutative, the steps for updating pool shares / liquidity are
// more complex / don't just alter the state.
// We should simplify this logic further in the future using multi-join equations.
func (p *Pool) CalcJoinPoolNoSwapShares(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, tokensJoined sdk.Coins, err error) {
// get all 'pool assets' (aka current pool liquidity + balancer weight)
poolAssetsByDenom, err := getPoolAssetsByDenom(p.GetAllPoolAssets())
if err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
}

err = ensureDenomInPool(poolAssetsByDenom, tokensIn)
if err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
}

// ensure that there aren't too many or too few assets in `tokensIn`
if tokensIn.Len() != p.NumAssets() {
return sdk.ZeroInt(), sdk.NewCoins(), errors.New("no-swap joins require LP'ing with all assets in pool")
Copy link
Member

Choose a reason for hiding this comment

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

Just trying to understand:

Why do you emphasize "no-swap joins"?

CalcJoinPoolNoSwapShares is called from CalcJoinPoolShares which is called from regular JoinPool that does swaps. I also noticed a similar check n CalcJoinPoolShares.

My point is that it seems that this is relevant for joins with the swaps as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This specific function is for no-swap joins, so the errors returned by it specify that it is a no-swap pool join (this specific line wouldn't error if this was a join with swaps)

Even though CalcJoinPoolNoSwapShares is still called in CalcJoinPoolShares, it is called in the "no-swap"/"all-asset"/"exact ratio" join component of it, and all pool join logic related to swaps is handled elsewhere in that function

Copy link
Member

Choose a reason for hiding this comment

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

Awesome, thank you! Just wanted to make sure that this check was intentional to have on the path for regular joins as well

}

// execute a no-swap join with as many tokens as possible given a perfect ratio:
// * numShares is how many shares are perfectly matched.
// * remainingTokensIn is how many coins we have left to join that have not already been used.
numShares, remainingTokensIn, err := cfmm_common.MaximalExactRatioJoin(p, sdk.Context{}, tokensIn)
AlpinYukseloglu marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
}

// ensure that no more tokens have been joined than is possible with the given `tokensIn`
tokensJoined = tokensIn.Sub(remainingTokensIn)
if tokensJoined.IsAnyGT(tokensIn) {
return sdk.ZeroInt(), sdk.NewCoins(), errors.New("an error has occurred, more coins joined than token In")
}

return numShares, tokensJoined, nil
}

// calcJoinSingleAssetTokensIn attempts to calculate single
// asset join for all tokensIn given totalShares in pool,
// poolAssetsByDenom and swapFee. totalShares is the number
Expand Down
22 changes: 22 additions & 0 deletions x/gamm/pool-models/balancer/pool_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,28 @@ func (suite *KeeperTestSuite) TestCalcJoinPoolShares() {
),
expectShares: sdk.NewInt(100_000_000),
},
{
// Input assets do not exist in pool
// P_issued = 1e20 * 1e-12 = 1e8
name: "attempt join with denoms not in pool",
swapFee: sdk.MustNewDecFromStr("0"),
poolAssets: []balancer.PoolAsset{
{
Token: sdk.NewInt64Coin("uosmo", 1_000_000_000_000),
Weight: sdk.NewInt(100),
},
{
Token: sdk.NewInt64Coin("uatom", 1_000_000_000_000),
Weight: sdk.NewInt(100),
},
},
tokensIn: sdk.NewCoins(
sdk.NewInt64Coin("foo", 1),
sdk.NewInt64Coin("baz", 1),
),
expectShares: sdk.NewInt(0),
expErr: types.ErrDenomNotFoundInPool,
},
}
testCases = append(testCases, calcSingleAssetJoinTestCases...)

Expand Down
Loading