Skip to content

Commit

Permalink
[x/gamm][stableswap]: Create scaling/descaling functions for token in…
Browse files Browse the repository at this point in the history
…puts/outputs (#2904)

Closes: #2902

## What is the purpose of the change

This PR adds a scaling function for token inputs. Depending on what level we want to handle rounding at (at the scaling function level vs. at the `SwapExactAmountIn` level), we can either use this function for outputs as well or create a second one that handles outputs with different rounding.

I personally think having one function with rounding handled at a higher level of abstraction is cleaner because it has less repeat code, but I can see the security argument for having multiple scaling functions that round differently for each specific use (cc: @ValarDragon)

## Brief Changelog

- Add `scaledInput` function and tests

## Testing and Verifying

- The function added in this PR is tested in `pool_test.go` under `TestScaledInput`

## Documentation and Release Note

  - Does this pull request introduce a new feature or user-facing behavior changes? (no)
  - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? (no)
  - How is the feature or change documented? (not documented)
  • Loading branch information
AlpinYukseloglu authored Sep 30, 2022
1 parent f573904 commit b9773dc
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 15 deletions.
14 changes: 13 additions & 1 deletion x/gamm/pool-models/stableswap/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ var _ types.PoolI = &Pool{}
func NewStableswapPool(poolId uint64,
stableswapPoolParams PoolParams, initialLiquidity sdk.Coins,
scalingFactors []uint64, scalingFactorController string,
futureGovernor string) (Pool, error) {
futureGovernor string,
) (Pool, error) {
if len(scalingFactors) == 0 {
scalingFactors = make([]uint64, len(initialLiquidity))
for i := range scalingFactors {
Expand Down Expand Up @@ -104,6 +105,17 @@ func (p Pool) NumAssets() int {
return len(p.PoolLiquidity)
}

// scaledInput returns scaled input tokens for usage in AMM equations
func (p Pool) scaleInputAmount(input sdk.Coin, roundingDirection osmomath.RoundingDirection) (osmomath.BigDec, error) {
liquidityIndexes := p.getLiquidityIndexMap()
scalingFactor := p.GetScalingFactorByLiquidityIndex(liquidityIndexes[input.Denom])
scaledAmount, err := osmomath.DivIntByU64ToBigDec(input.Amount, scalingFactor, roundingDirection)
if err != nil {
return osmomath.BigDec{}, err
}
return scaledAmount, nil
}

// getDescaledPoolAmts gets descaled amount of given denom and amount
// TODO: Review rounding of this in all contexts
func (p Pool) getDescaledPoolAmt(denom string, amount osmomath.BigDec) osmomath.BigDec {
Expand Down
133 changes: 119 additions & 14 deletions x/gamm/pool-models/stableswap/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ func TestReorderReservesAndScalingFactors(t *testing.T) {
sdk.NewInt64Coin("asset/b", 2000000000),
sdk.NewInt64Coin("asset/a", 1000000000),
sdk.NewInt64Coin("asset/d", 4000000000),
sdk.NewInt64Coin("asset/e", 5000000000)},
sdk.NewInt64Coin("asset/e", 5000000000),
},
reordedScalingFactors: []uint64{3, 2, 1, 4, 5},
},
"two of 5 assets in pool v2": {
Expand All @@ -85,7 +86,8 @@ func TestReorderReservesAndScalingFactors(t *testing.T) {
sdk.NewInt64Coin("asset/b", 2000000000),
sdk.NewInt64Coin("asset/a", 1000000000),
sdk.NewInt64Coin("asset/c", 3000000000),
sdk.NewInt64Coin("asset/d", 4000000000)},
sdk.NewInt64Coin("asset/d", 4000000000),
},
reordedScalingFactors: []uint64{5, 2, 1, 3, 4},
},
"asset 1 doesn't exist": {
Expand Down Expand Up @@ -150,7 +152,8 @@ func TestScaledSortedPoolReserves(t *testing.T) {
poolAssets: twoUnevenStablePoolAssets,
scalingFactors: []uint64{10, 5},
expReserves: []osmomath.BigDec{
osmomath.NewBigDec(2000000000 / 5), osmomath.NewBigDec(1000000000 / 10)},
osmomath.NewBigDec(2000000000 / 5), osmomath.NewBigDec(1000000000 / 10),
},
},
"even two-asset pool with even, massive scaling factors greater than 1": {
denoms: [2]string{"foo", "bar"},
Expand All @@ -163,30 +166,33 @@ func TestScaledSortedPoolReserves(t *testing.T) {
poolAssets: fiveUnevenStablePoolAssets,
scalingFactors: []uint64{1, 1, 1, 1, 1},
expReserves: []osmomath.BigDec{
baseEvenAmt.MulInt64(3), // {"asset/c", baseEvenAmt.MulInt64(3)},
baseEvenAmt.MulInt64(4), // {"asset/d", baseEvenAmt.MulInt64(4)},
baseEvenAmt, // {"asset/a", baseEvenAmt},
baseEvenAmt.MulInt64(2), // {"asset/b", baseEvenAmt.MulInt64(2)},
baseEvenAmt.MulInt64(5)}, // {"asset/e", baseEvenAmt.MulInt64(5)}},
baseEvenAmt.MulInt64(3), // {"asset/c", baseEvenAmt.MulInt64(3)},
baseEvenAmt.MulInt64(4), // {"asset/d", baseEvenAmt.MulInt64(4)},
baseEvenAmt, // {"asset/a", baseEvenAmt},
baseEvenAmt.MulInt64(2), // {"asset/b", baseEvenAmt.MulInt64(2)},
baseEvenAmt.MulInt64(5),
}, // {"asset/e", baseEvenAmt.MulInt64(5)}},
},
"five asset pool, scaling factors = 1,2,3,4,5": {
denoms: [2]string{"asset/a", "asset/e"},
poolAssets: fiveUnevenStablePoolAssets,
scalingFactors: []uint64{1, 2, 3, 4, 5},
expReserves: []osmomath.BigDec{
baseEvenAmt, // {"asset/a", baseEvenAmt},
baseEvenAmt, // {"asset/e", baseEvenAmt},
baseEvenAmt, // {"asset/b", baseEvenAmt},
baseEvenAmt, // {"asset/c", baseEvenAmt},
baseEvenAmt}, // {"asset/d", baseEvenAmt}},
baseEvenAmt, // {"asset/a", baseEvenAmt},
baseEvenAmt, // {"asset/e", baseEvenAmt},
baseEvenAmt, // {"asset/b", baseEvenAmt},
baseEvenAmt, // {"asset/c", baseEvenAmt},
baseEvenAmt,
}, // {"asset/d", baseEvenAmt}},
},
"max scaling factors": {
denoms: [2]string{"foo", "bar"},
poolAssets: twoEvenStablePoolAssets,
scalingFactors: []uint64{(1 << 62), (1 << 62)},
expReserves: []osmomath.BigDec{
osmomath.NewBigDec(1000000000).QuoInt64(int64(1 << 62)),
osmomath.NewBigDec(1000000000).QuoInt64(int64(1 << 62))},
osmomath.NewBigDec(1000000000).QuoInt64(int64(1 << 62)),
},
},
"zero scaling factor": {
denoms: [2]string{"foo", "bar"},
Expand Down Expand Up @@ -397,3 +403,102 @@ func TestGetDescaledPoolAmts(t *testing.T) {
})
}
}

func TestScaledInput(t *testing.T) {
tests := map[string]struct {
input sdk.Coin
rounding osmomath.RoundingDirection
poolAssets sdk.Coins
scalingFactors []uint64
expOutput osmomath.BigDec
expError bool
}{
"even two-asset pool with default scaling factors": {
input: sdk.NewCoin("bar", sdk.NewInt(100)),
rounding: osmomath.RoundDown,
poolAssets: twoEvenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
expOutput: osmomath.NewBigDec(100),
expError: false,
},
"uneven two-asset pool with default scaling factors": {
input: sdk.NewCoin("foo", sdk.NewInt(200)),
rounding: osmomath.RoundDown,
poolAssets: twoUnevenStablePoolAssets,
scalingFactors: defaultTwoAssetScalingFactors,
expOutput: osmomath.NewBigDec(200),
expError: false,
},
"even two-asset pool with uneven scaling factors greater than 1": {
input: sdk.NewCoin("bar", sdk.NewInt(100)),
rounding: osmomath.RoundDown,
poolAssets: twoUnevenStablePoolAssets,
scalingFactors: []uint64{10, 5},
expOutput: osmomath.NewBigDec(10),
expError: false,
},
"even two-asset pool with even, massive scaling factors greater than 1": {
input: sdk.NewCoin("foo", sdk.NewInt(100)),
rounding: osmomath.RoundDown,
poolAssets: twoEvenStablePoolAssets,
scalingFactors: []uint64{10000000000, 10_000_000_000},
expOutput: osmomath.NewDecWithPrec(100, 10),
expError: false,
},
"five asset pool scaling factors = 1": {
input: sdk.NewCoin("asset/c", sdk.NewInt(100)),
rounding: osmomath.RoundDown,
poolAssets: fiveUnevenStablePoolAssets,
scalingFactors: []uint64{1, 1, 1, 1, 1},
expOutput: osmomath.NewBigDec(100),
expError: false,
},
"five asset pool scaling factors = 1,2,3,4,5": {
input: sdk.NewCoin("asset/d", sdk.NewInt(100)),
rounding: osmomath.RoundDown,
poolAssets: fiveUnevenStablePoolAssets,
scalingFactors: []uint64{1, 2, 3, 4, 5},
expOutput: osmomath.NewBigDec(25),
expError: false,
},
"max scaling factors on small token inputs": {
input: sdk.NewCoin("foo", sdk.NewInt(10)),
rounding: osmomath.RoundDown,
poolAssets: twoEvenStablePoolAssets,
scalingFactors: []uint64{(1 << 62), (1 << 62)},
expOutput: osmomath.NewBigDec(10).QuoInt64(1 << 62),
expError: false,
},
"zero scaling factor": {
input: sdk.NewCoin("bar", sdk.NewInt(100)),
rounding: osmomath.RoundDown,
poolAssets: twoEvenStablePoolAssets,
scalingFactors: []uint64{0, 1},
expError: true,
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// we create the pool directly to bypass checks in NewStableswapPool()
p := Pool{
Address: types.NewPoolAddress(defaultPoolId).String(),
Id: defaultPoolId,
PoolParams: defaultStableswapPoolParams,
TotalShares: sdk.NewCoin(types.GetPoolShareDenom(defaultPoolId), types.InitPoolSharesSupply),
PoolLiquidity: tc.poolAssets,
ScalingFactor: tc.scalingFactors,
FuturePoolGovernor: defaultFutureGovernor,
}

scaledInput, err := p.scaleInputAmount(tc.input, tc.rounding)

if !tc.expError {
require.NoError(t, err, "test: %s", name)
require.Equal(t, tc.expOutput, scaledInput)
}

osmoassert.ConditionalError(t, tc.expError, err)
})
}
}

0 comments on commit b9773dc

Please sign in to comment.