Skip to content

Commit

Permalink
[x/gamm][stableswap]: Add inverse join/exit tests, fix single asset j…
Browse files Browse the repository at this point in the history
…oin bug, and remove uneven ratio joins (osmosis-labs#3102)

* add tests for 10-asset pools with 10B per asset

* add max post-scaled asset check and create pool tests

* add sanity tests for new swap guardrails

* move max scaled asset amt to constant

* add join-pool-internal tests for new functionality

* fix single join bug, remove uneven ratio joins, and add inverse join tests

* add error checks to single asset joins

* fix mistake in test case

* remove commented line

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
Co-authored-by: Dev Ojha <dojha@berkeley.edu>
  • Loading branch information
3 people authored and Ruslan Akhtariev committed Nov 1, 2022
1 parent c25ff06 commit 2c73f47
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 17 deletions.
26 changes: 12 additions & 14 deletions x/gamm/pool-models/stableswap/amm.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,18 @@ func (p *Pool) joinPoolSharesInternal(ctx sdk.Context, tokensIn sdk.Coins, swapF
}
if len(tokensIn) == 1 && tokensIn[0].Amount.GT(sdk.OneInt()) {
numShares, err = p.calcSingleAssetJoinShares(tokensIn[0], swapFee)
if err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
}

newLiquidity = tokensIn

p.updatePoolForJoin(newLiquidity, numShares)

if err = validatePoolAssets(p.PoolLiquidity, p.ScalingFactor); err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
}

return numShares, newLiquidity, err
} else if len(tokensIn) != p.NumAssets() {
return sdk.ZeroInt(), sdk.NewCoins(), errors.New(
Expand All @@ -396,20 +407,7 @@ func (p *Pool) joinPoolSharesInternal(ctx sdk.Context, tokensIn sdk.Coins, swapF
}
p.updatePoolForJoin(tokensIn.Sub(remCoins), numShares)

tokensJoined := tokensIn
for _, coin := range remCoins {
// TODO: Perhaps add a method to skip if this is too small.
if coin.Amount.GT(sdk.OneInt()) {
newShare, err := p.calcSingleAssetJoinShares(coin, swapFee)
if err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
}
p.updatePoolForJoin(sdk.NewCoins(coin), newShare)
numShares = numShares.Add(newShare)
} else {
tokensJoined = tokensJoined.Sub(sdk.NewCoins(coin))
}
}
tokensJoined := tokensIn.Sub(remCoins)

if err = validatePoolAssets(p.PoolLiquidity, p.ScalingFactor); err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
Expand Down
6 changes: 3 additions & 3 deletions x/gamm/pool-models/stableswap/amm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -866,9 +866,9 @@ func TestJoinPoolSharesInternal(t *testing.T) {
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
swapFee: sdk.ZeroDec(),
expNumShare: sdk.NewIntFromUint64(10000000500000000000),
expTokensJoined: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(tenPercentOfTwoPoolRaw)), sdk.NewCoin("bar", sdk.NewInt(10+tenPercentOfTwoPoolRaw))),
expPoolAssets: twoAssetPlusTenPercent.Add(sdk.NewCoin("bar", sdk.NewInt(10))),
expNumShare: sdk.NewIntFromUint64(10000000000000000000),
expTokensJoined: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(tenPercentOfTwoPoolRaw)), sdk.NewCoin("bar", sdk.NewInt(tenPercentOfTwoPoolRaw))),
expPoolAssets: twoAssetPlusTenPercent,
expectPass: true,
},
"all-asset pool join attempt exceeds max scaled asset amount": {
Expand Down
152 changes: 152 additions & 0 deletions x/gamm/pool-models/stableswap/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/osmosis-labs/osmosis/v12/app/apptesting/osmoassert"
"github.com/osmosis-labs/osmosis/v12/osmomath"
"github.com/osmosis-labs/osmosis/v12/x/gamm/pool-models/internal/cfmm_common"
"github.com/osmosis-labs/osmosis/v12/x/gamm/types"
)

Expand Down Expand Up @@ -639,3 +640,154 @@ func TestSwapInAmtGivenOut(t *testing.T) {
})
}
}

func TestInverseJoinPoolExitPool(t *testing.T) {
hundredFoo := sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100)))
thousandAssetA := sdk.NewCoins(sdk.NewCoin("asset/a", sdk.NewInt(1000)))
tenPercentOfTwoPoolRaw := int64(1000000000 / 10)
tenPercentOfTwoPoolCoins := sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(int64(1000000000/10))), sdk.NewCoin("bar", sdk.NewInt(int64(1000000000/10))))
type testcase struct {
tokensIn sdk.Coins
poolAssets sdk.Coins
unevenJoinedTokens sdk.Coins
scalingFactors []uint64
swapFee sdk.Dec
expectPass bool
}

tests := map[string]testcase{
"[single asset join] even two asset pool, no swap fee": {
tokensIn: hundredFoo,
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
swapFee: sdk.ZeroDec(),
expectPass: true,
},
"[single asset join] uneven two asset pool, no swap fee": {
tokensIn: hundredFoo,
poolAssets: twoUnevenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
swapFee: sdk.ZeroDec(),
expectPass: true,
},
"[single asset join] even 3-asset pool, no swap fee": {
tokensIn: thousandAssetA,
poolAssets: threeEvenStablePoolAssets,
scalingFactors: defaultThreeAssetScalingFactors,
swapFee: sdk.ZeroDec(),
expectPass: true,
},
"[single asset join] uneven 3-asset pool, no swap fee": {
tokensIn: thousandAssetA,
poolAssets: threeUnevenStablePoolAssets,
scalingFactors: defaultThreeAssetScalingFactors,
swapFee: sdk.ZeroDec(),
expectPass: true,
},
"[single asset join] even two asset pool, default swap fee": {
tokensIn: hundredFoo,
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
swapFee: defaultSwapFee,
expectPass: true,
},
"[single asset join] uneven two asset pool, default swap fee": {
tokensIn: hundredFoo,
poolAssets: twoUnevenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
swapFee: defaultSwapFee,
expectPass: true,
},
"[single asset join] even 3-asset pool, default swap fee": {
tokensIn: thousandAssetA,
poolAssets: threeEvenStablePoolAssets,
scalingFactors: defaultThreeAssetScalingFactors,
swapFee: defaultSwapFee,
expectPass: true,
},
"[single asset join] uneven 3-asset pool, default swap fee": {
tokensIn: thousandAssetA,
poolAssets: threeUnevenStablePoolAssets,
scalingFactors: defaultThreeAssetScalingFactors,
swapFee: defaultSwapFee,
expectPass: true,
},
"[single asset join] even 3-asset pool, 0.03 swap fee": {
tokensIn: thousandAssetA,
poolAssets: threeEvenStablePoolAssets,
scalingFactors: defaultThreeAssetScalingFactors,
swapFee: sdk.MustNewDecFromStr("0.03"),
expectPass: true,
},
"[single asset join] uneven 3-asset pool, 0.03 swap fee": {
tokensIn: thousandAssetA,
poolAssets: threeUnevenStablePoolAssets,
scalingFactors: defaultThreeAssetScalingFactors,
swapFee: sdk.MustNewDecFromStr("0.03"),
expectPass: true,
},

"[all asset join] even two asset pool, same tokenIn ratio": {
tokensIn: tenPercentOfTwoPoolCoins,
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
swapFee: sdk.ZeroDec(),
expectPass: true,
},
"[all asset join] even two asset pool, different tokenIn ratio with pool": {
tokensIn: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(tenPercentOfTwoPoolRaw)), sdk.NewCoin("bar", sdk.NewInt(10+tenPercentOfTwoPoolRaw))),
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
swapFee: sdk.ZeroDec(),
expectPass: true,
},
"[all asset join] even two asset pool, different tokenIn ratio with pool, nonzero swap fee": {
tokensIn: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(tenPercentOfTwoPoolRaw)), sdk.NewCoin("bar", sdk.NewInt(10+tenPercentOfTwoPoolRaw))),
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
swapFee: defaultSwapFee,
expectPass: true,
},
"[all asset join] even two asset pool, no tokens in": {
tokensIn: sdk.NewCoins(),
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
swapFee: sdk.ZeroDec(),
expectPass: true,
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
ctx := sdk.Context{}
p := poolStructFromAssets(tc.poolAssets, tc.scalingFactors)

// we join then exit the pool
shares, err := p.JoinPool(ctx, tc.tokensIn, tc.swapFee)
tokenOut, err := p.ExitPool(ctx, shares, defaultExitFee)

// if single asset join, we swap output tokens to input denom to test the full inverse relationship
if len(tc.tokensIn) == 1 {
tokenOutAmt, err := cfmm_common.SwapAllCoinsToSingleAsset(&p, ctx, tokenOut, tc.tokensIn[0].Denom)
require.NoError(t, err)
tokenOut = sdk.NewCoins(sdk.NewCoin(tc.tokensIn[0].Denom, tokenOutAmt))
}

// if single asset join, we expect output token swapped into the input denom to be input minus swap fee
var expectedTokenOut sdk.Coins
if len(tc.tokensIn) == 1 {
expectedAmt := (tc.tokensIn[0].Amount.ToDec().Mul(sdk.OneDec().Sub(tc.swapFee))).TruncateInt()
expectedTokenOut = sdk.NewCoins(sdk.NewCoin(tc.tokensIn[0].Denom, expectedAmt))
} else {
expectedTokenOut = tc.tokensIn
}

if tc.expectPass {
finalPoolLiquidity := p.GetTotalPoolLiquidity(ctx)
require.True(t, tokenOut.IsAllLTE(expectedTokenOut))
require.True(t, finalPoolLiquidity.IsAllGTE(tc.poolAssets))
}
osmoassert.ConditionalError(t, !tc.expectPass, err)
})
}
}

0 comments on commit 2c73f47

Please sign in to comment.