Skip to content

Commit

Permalink
Stableswap implement JoinPoolNoSwap (#2942)
Browse files Browse the repository at this point in the history
* ...

* add tests and denom validation check

* make format

Co-authored-by: alpo <yukseloglua@berkeley.edu>
Co-authored-by: alpo <62043214+AlpinYukseloglu@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 24, 2022
1 parent 184a85c commit 805f80c
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 9 deletions.
3 changes: 2 additions & 1 deletion x/gamm/keeper/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ func (suite *KeeperTestSuite) TestGetPoolAndPoke() {
},
sdk.NewCoins(sdk.NewCoin(defaultAcctFunds[0].Denom, defaultAcctFunds[0].Amount.QuoRaw(2)), sdk.NewCoin(defaultAcctFunds[1].Denom, defaultAcctFunds[1].Amount.QuoRaw(2))),
[]uint64{1, 1},
)},
),
},
}

for name, tc := range tests {
Expand Down
45 changes: 37 additions & 8 deletions x/gamm/pool-models/stableswap/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,19 +297,48 @@ func (p *Pool) CalcJoinPoolShares(ctx sdk.Context, tokensIn sdk.Coins, swapFee s
return pCopy.joinPoolSharesInternal(ctx, tokensIn, swapFee)
}

// TODO: implement this
func (p *Pool) CalcJoinPoolNoSwapShares(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, newLiquidity sdk.Coins, err error) {
return sdk.ZeroInt(), nil, err
// 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 the same tokens as in the pool.
//
// Returns the number of shares created, the amount of coins actually joined into the pool as not all may tokens may be joinable.
// If an all-asset join is not possible, returns an error.
func (p Pool) CalcJoinPoolNoSwapShares(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, tokensJoined sdk.Coins, err error) {
// ensure that there aren't too many or too few assets in `tokensIn`
if tokensIn.Len() != p.NumAssets() || !tokensIn.DenomsSubsetOf(p.GetTotalPoolLiquidity(ctx)) {
return sdk.ZeroInt(), sdk.NewCoins(), errors.New("no-swap joins require LP'ing with all assets in pool")
}

// 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, ctx, tokensIn)
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
}

func (p *Pool) JoinPool(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, err error) {
numShares, _, err = p.joinPoolSharesInternal(ctx, tokensIn, swapFee)
func (p *Pool) JoinPool(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (sdk.Int, error) {
numShares, _, err := p.joinPoolSharesInternal(ctx, tokensIn, swapFee)
return numShares, err
}

// TODO: implement this
func (p *Pool) JoinPoolNoSwap(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, err error) {
return sdk.ZeroInt(), err
func (p *Pool) JoinPoolNoSwap(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (sdk.Int, error) {
newShares, 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.updatePoolForJoin(tokensJoined, newShares)
return newShares, nil
}

func (p *Pool) ExitPool(ctx sdk.Context, exitingShares sdk.Int, exitFee sdk.Dec) (exitingCoins sdk.Coins, err error) {
Expand Down
172 changes: 172 additions & 0 deletions x/gamm/pool-models/stableswap/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,178 @@ func TestScaleCoin(t *testing.T) {
}
}

func TestCalcJoinPoolNoSwapShares(t *testing.T) {
tenPercentOfTwoPool := int64(1000000000 / 10)
tenPercentOfThreePool := int64(1000000 / 10)
tests := map[string]struct {
tokensIn sdk.Coins
poolAssets sdk.Coins
scalingFactors []uint64
expNumShare sdk.Int
expTokensJoined sdk.Coins
expPoolAssets sdk.Coins
expectPass bool
}{
"even two asset pool, same tokenIn ratio": {
tokensIn: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(tenPercentOfTwoPool)), sdk.NewCoin("bar", sdk.NewInt(tenPercentOfTwoPool))),
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
expNumShare: sdk.NewIntFromUint64(10000000000000000000),
expTokensJoined: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(tenPercentOfTwoPool)), sdk.NewCoin("bar", sdk.NewInt(tenPercentOfTwoPool))),
expPoolAssets: twoEvenStablePoolAssets,
expectPass: true,
},
"even two asset pool, different tokenIn ratio with pool": {
tokensIn: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(tenPercentOfTwoPool)), sdk.NewCoin("bar", sdk.NewInt(tenPercentOfTwoPool+1))),
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
expNumShare: sdk.NewIntFromUint64(10000000000000000000),
expTokensJoined: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(tenPercentOfTwoPool)), sdk.NewCoin("bar", sdk.NewInt(tenPercentOfTwoPool))),
expPoolAssets: twoEvenStablePoolAssets,
expectPass: true,
},
"uneven two asset pool, same tokenIn ratio": {
tokensIn: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(2*tenPercentOfTwoPool)), sdk.NewCoin("bar", sdk.NewInt(tenPercentOfTwoPool))),
poolAssets: twoUnevenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
expNumShare: sdk.NewIntFromUint64(10000000000000000000),
expTokensJoined: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(2*tenPercentOfTwoPool)), sdk.NewCoin("bar", sdk.NewInt(tenPercentOfTwoPool))),
expPoolAssets: twoUnevenStablePoolAssets,
expectPass: true,
},
"uneven two asset pool, different tokenIn ratio with pool": {
tokensIn: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(2*tenPercentOfTwoPool)), sdk.NewCoin("bar", sdk.NewInt(tenPercentOfTwoPool+1))),
poolAssets: twoUnevenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
expNumShare: sdk.NewIntFromUint64(10000000000000000000),
expTokensJoined: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(2*tenPercentOfTwoPool)), sdk.NewCoin("bar", sdk.NewInt(tenPercentOfTwoPool))),
expPoolAssets: twoUnevenStablePoolAssets,
expectPass: true,
},
"even three asset pool, same tokenIn ratio": {
tokensIn: sdk.NewCoins(sdk.NewCoin("asset/a", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/b", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/c", sdk.NewInt(tenPercentOfThreePool))),
poolAssets: threeEvenStablePoolAssets,
scalingFactors: defaultThreeAssetScalingFactors,
expNumShare: sdk.NewIntFromUint64(10000000000000000000),
expTokensJoined: sdk.NewCoins(sdk.NewCoin("asset/a", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/b", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/c", sdk.NewInt(tenPercentOfThreePool))),
expPoolAssets: threeEvenStablePoolAssets,
expectPass: true,
},
"even three asset pool, different tokenIn ratio with pool": {
tokensIn: sdk.NewCoins(sdk.NewCoin("asset/a", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/b", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/c", sdk.NewInt(tenPercentOfThreePool+1))),
poolAssets: threeEvenStablePoolAssets,
scalingFactors: defaultThreeAssetScalingFactors,
expNumShare: sdk.NewIntFromUint64(10000000000000000000),
expTokensJoined: sdk.NewCoins(sdk.NewCoin("asset/a", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/b", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/c", sdk.NewInt(tenPercentOfThreePool))),
expPoolAssets: threeEvenStablePoolAssets,
expectPass: true,
},
"uneven three asset pool, same tokenIn ratio": {
tokensIn: sdk.NewCoins(sdk.NewCoin("asset/a", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/b", sdk.NewInt(2*tenPercentOfThreePool)), sdk.NewCoin("asset/c", sdk.NewInt(3*tenPercentOfThreePool))),
poolAssets: threeUnevenStablePoolAssets,
scalingFactors: defaultThreeAssetScalingFactors,
expNumShare: sdk.NewIntFromUint64(10000000000000000000),
expTokensJoined: sdk.NewCoins(sdk.NewCoin("asset/a", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/b", sdk.NewInt(2*tenPercentOfThreePool)), sdk.NewCoin("asset/c", sdk.NewInt(3*tenPercentOfThreePool))),
expPoolAssets: threeUnevenStablePoolAssets,
expectPass: true,
},
"uneven three asset pool, different tokenIn ratio with pool": {
tokensIn: sdk.NewCoins(sdk.NewCoin("asset/a", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/b", sdk.NewInt(2*tenPercentOfThreePool)), sdk.NewCoin("asset/c", sdk.NewInt(3*tenPercentOfThreePool+1))),
poolAssets: threeUnevenStablePoolAssets,
scalingFactors: defaultThreeAssetScalingFactors,
expNumShare: sdk.NewIntFromUint64(10000000000000000000),
expTokensJoined: sdk.NewCoins(sdk.NewCoin("asset/a", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/b", sdk.NewInt(2*tenPercentOfThreePool)), sdk.NewCoin("asset/c", sdk.NewInt(3*tenPercentOfThreePool))),
expPoolAssets: threeUnevenStablePoolAssets,
expectPass: true,
},
"uneven three asset pool, uneven scaling factors": {
tokensIn: sdk.NewCoins(sdk.NewCoin("asset/a", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/b", sdk.NewInt(2*tenPercentOfThreePool)), sdk.NewCoin("asset/c", sdk.NewInt(3*tenPercentOfThreePool))),
poolAssets: threeUnevenStablePoolAssets,
scalingFactors: []uint64{5, 9, 175},
expNumShare: sdk.NewIntFromUint64(10000000000000000000),
expTokensJoined: sdk.NewCoins(sdk.NewCoin("asset/a", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/b", sdk.NewInt(2*tenPercentOfThreePool)), sdk.NewCoin("asset/c", sdk.NewInt(3*tenPercentOfThreePool))),
expPoolAssets: threeUnevenStablePoolAssets,
expectPass: true,
},

// error catching
"even two asset pool, no-swap join attempt with one asset": {
tokensIn: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(tenPercentOfTwoPool))),
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
expNumShare: sdk.NewIntFromUint64(0),
expTokensJoined: sdk.Coins{},
expPoolAssets: twoEvenStablePoolAssets,
expectPass: false,
},
"even two asset pool, no-swap join attempt with one valid and one invalid asset": {
tokensIn: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(tenPercentOfTwoPool)), sdk.NewCoin("baz", sdk.NewInt(tenPercentOfTwoPool))),
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
expNumShare: sdk.NewIntFromUint64(0),
expTokensJoined: sdk.Coins{},
expPoolAssets: twoEvenStablePoolAssets,
expectPass: false,
},
"even two asset pool, no-swap join attempt with two invalid assets": {
tokensIn: sdk.NewCoins(sdk.NewCoin("baz", sdk.NewInt(tenPercentOfTwoPool)), sdk.NewCoin("qux", sdk.NewInt(tenPercentOfTwoPool))),
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
expNumShare: sdk.NewIntFromUint64(0),
expTokensJoined: sdk.Coins{},
expPoolAssets: twoEvenStablePoolAssets,
expectPass: false,
},
"even three asset pool, no-swap join attempt with an invalid asset": {
tokensIn: sdk.NewCoins(sdk.NewCoin("asset/a", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("asset/b", sdk.NewInt(tenPercentOfThreePool)), sdk.NewCoin("qux", sdk.NewInt(tenPercentOfThreePool))),
poolAssets: threeEvenStablePoolAssets,
scalingFactors: defaultThreeAssetScalingFactors,
expNumShare: sdk.NewIntFromUint64(0),
expTokensJoined: sdk.Coins{},
expPoolAssets: threeEvenStablePoolAssets,
expectPass: false,
},
"single asset pool, no-swap join attempt with one asset": {
tokensIn: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(sdk.MaxSortableDec.TruncateInt64()))),
poolAssets: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1))),
scalingFactors: []uint64{1},
expNumShare: sdk.NewIntFromUint64(0),
expTokensJoined: sdk.Coins{},
expPoolAssets: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1))),
expectPass: false,
},
"attempt joining pool with no assets in it": {
tokensIn: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1))),
poolAssets: sdk.Coins{},
scalingFactors: []uint64{},
expNumShare: sdk.NewIntFromUint64(0),
expTokensJoined: sdk.Coins{},
expPoolAssets: sdk.Coins{},
expectPass: false,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
ctx := sdk.Context{}
pool := poolStructFromAssets(test.poolAssets, test.scalingFactors)
numShare, tokensJoined, err := pool.CalcJoinPoolNoSwapShares(ctx, test.tokensIn, pool.GetSwapFee(ctx))

if test.expectPass {
require.NoError(t, err)
require.Equal(t, test.expPoolAssets, pool.GetTotalPoolLiquidity(ctx))
require.Equal(t, test.expNumShare, numShare)
require.Equal(t, test.expTokensJoined, tokensJoined)
} else {
require.Error(t, err)
require.Equal(t, test.expPoolAssets, pool.GetTotalPoolLiquidity(ctx))
require.Equal(t, test.expNumShare, numShare)
require.Equal(t, test.expTokensJoined, tokensJoined)
}
})
}
}

func TestSwapOutAmtGivenIn(t *testing.T) {
tests := map[string]struct {
poolAssets sdk.Coins
Expand Down

0 comments on commit 805f80c

Please sign in to comment.