Repository: osmosis-labs/osmosis
Pull request #3933 (2023-01-06 11:26:01)

Gamm stableswap improvements (backport #3839)
This is an automatic backport of pull request #3839 done by [Mergify](https://mergify.com).
Cherry-pick of 2ac5d3561928b0f60bd7f9451b9168f6e1ad5344 has failed:
```
On branch mergify/bp/v14.x/pr-3839
Your branch is up to date with 'origin/v14.x'.

You are currently cherry-picking commit 2ac5d356.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --skip" to skip this patch)
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Changes to be committed:
	modified:   osmomath/binary_search.go
	modified:   osmomath/binary_search_test.go
	modified:   x/gamm/pool-models/stableswap/README.md
	modified:   x/gamm/pool-models/stableswap/amm.go
	modified:   x/gamm/pool-models/stableswap/pool.go
	modified:   x/gamm/pool-models/stableswap/pool_test.go
	modified:   x/gamm/types/errors.go

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   x/gamm/pool-models/stableswap/util_test.go

```


To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/github/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally

---


<details>
<summary>Mergify commands and options</summary>

<br />

More conditions and actions can be found in the [documentation](https://docs.mergify.com/).

You can also trigger Mergify actions by commenting on this pull request:

- `@Mergifyio refresh` will re-evaluate the rules
- `@Mergifyio rebase` will rebase this PR on its base branch
- `@Mergifyio update` will merge the base branch into this PR
- `@Mergifyio backport <destination>` will backport this PR on `<destination>` branch

Additionally, on Mergify [dashboard](https://dashboard.mergify.com/) you can:

- look at your merge queues
- generate the Mergify configuration with the config editor.

Finally, you can contact us on https://mergify.com
</details>

p0mvn: This seems to be blocking: https://github.com/osmosis-labs/osmosis/pull/3934

I'm going to merge to continue with the v14.x merge for the final RC

Patch:
diff --git a/go.mod b/go.mod
index c744452e7..0b263d8ae 100644
--- a/go.mod
+++ b/go.mod
@@ -17,7 +17,7 @@ require (
 	github.com/mattn/go-sqlite3 v1.14.16
 	github.com/ory/dockertest/v3 v3.9.1
 	github.com/osmosis-labs/go-mutesting v0.0.0-20221208041716-b43bcd97b3b3
-	github.com/osmosis-labs/osmosis/osmomath v0.0.0-20230106110532-e17f2f459464
+	github.com/osmosis-labs/osmosis/osmomath v0.0.0-20230106133904-bf95f2df4908
 	github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230106095152-4f77cc5e42af
 	github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230106110415-61e4300ada92
 	github.com/pkg/errors v0.9.1
diff --git a/go.sum b/go.sum
index 2f56e5241..021abbf84 100644
--- a/go.sum
+++ b/go.sum
@@ -853,8 +853,8 @@ github.com/osmosis-labs/cosmos-sdk v0.45.1-0.20221118211718-545aed73e94e h1:A3by
 github.com/osmosis-labs/cosmos-sdk v0.45.1-0.20221118211718-545aed73e94e/go.mod h1:rud0OaBIuq3+qOqtwT4SR7Q7iSzRp7w41fjninTjfnQ=
 github.com/osmosis-labs/go-mutesting v0.0.0-20221208041716-b43bcd97b3b3 h1:YlmchqTmlwdWSmrRmXKR+PcU96ntOd8u10vTaTZdcNY=
 github.com/osmosis-labs/go-mutesting v0.0.0-20221208041716-b43bcd97b3b3/go.mod h1:lV6KnqXYD/ayTe7310MHtM3I2q8Z6bBfMAi+bhwPYtI=
-github.com/osmosis-labs/osmosis/osmomath v0.0.0-20230106110532-e17f2f459464 h1:2562qeTuCCb1IQBcbKjQgVD+cpPxVMzi//Dr/1uQbGc=
-github.com/osmosis-labs/osmosis/osmomath v0.0.0-20230106110532-e17f2f459464/go.mod h1:KrzYoNtnWUH75rj1XAsSR4nymlHFU7jeVOx7/1KMe0k=
+github.com/osmosis-labs/osmosis/osmomath v0.0.0-20230106133904-bf95f2df4908 h1:N7JvlXT8N82iN7NDzX6CXFOFrpFTWqSfJuAZcInQ+/Y=
+github.com/osmosis-labs/osmosis/osmomath v0.0.0-20230106133904-bf95f2df4908/go.mod h1:KrzYoNtnWUH75rj1XAsSR4nymlHFU7jeVOx7/1KMe0k=
 github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230106095152-4f77cc5e42af h1:/UyyIUTH2FZaN7xULcA2SQ6+bKD9fsau4PJkYWMir3g=
 github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230106095152-4f77cc5e42af/go.mod h1:K4de+n3DtLdueen98dOzaRXZvqMd8JvigL8O1xW445o=
 github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230106110415-61e4300ada92 h1:aXAru0jzeTjrSmFEcqIXUI23yaE0RqpwKg04HK1sYcs=
diff --git a/osmomath/binary_search.go b/osmomath/binary_search.go
index 4b7878274..d43c9dd3b 100644
--- a/osmomath/binary_search.go
+++ b/osmomath/binary_search.go
@@ -69,7 +69,12 @@ func (e ErrTolerance) Compare(expected sdk.Int, actual sdk.Int) int {
 	}
 	// Check multiplicative tolerance equations
 	if !e.MultiplicativeTolerance.IsNil() && !e.MultiplicativeTolerance.IsZero() {
-		errTerm := diff.Quo(sdk.MinInt(expected.Abs(), actual.Abs()).ToDec())
+		minValue := sdk.MinInt(expected.Abs(), actual.Abs())
+		if minValue.IsZero() {
+			return comparisonSign
+		}
+
+		errTerm := diff.Quo(minValue.ToDec())
 		if errTerm.GT(e.MultiplicativeTolerance) {
 			return comparisonSign
 		}
@@ -121,7 +126,12 @@ func (e ErrTolerance) CompareBigDec(expected BigDec, actual BigDec) int {
 	}
 	// Check multiplicative tolerance equations
 	if !e.MultiplicativeTolerance.IsNil() && !e.MultiplicativeTolerance.IsZero() {
-		errTerm := diff.Quo(MinDec(expected.Abs(), actual.Abs()))
+		minValue := MinDec(expected.Abs(), actual.Abs())
+		if minValue.IsZero() {
+			return comparisonSign
+		}
+
+		errTerm := diff.Quo(minValue)
 		// fmt.Printf("err term %v\n", errTerm)
 		if errTerm.GT(BigDecFromSDKDec(e.MultiplicativeTolerance)) {
 			return comparisonSign
@@ -141,14 +151,19 @@ func BinarySearch(f func(input sdk.Int) (sdk.Int, error),
 	errTolerance ErrTolerance,
 	maxIterations int,
 ) (sdk.Int, error) {
-	// Setup base case of loop
-	curEstimate := lowerbound.Add(upperbound).QuoRaw(2)
-	curOutput, err := f(curEstimate)
-	if err != nil {
-		return sdk.Int{}, err
-	}
+	var (
+		curEstimate, curOutput sdk.Int
+		err                    error
+	)
+
 	curIteration := 0
 	for ; curIteration < maxIterations; curIteration += 1 {
+		curEstimate = lowerbound.Add(upperbound).QuoRaw(2)
+		curOutput, err = f(curEstimate)
+		if err != nil {
+			return sdk.Int{}, err
+		}
+
 		compRes := errTolerance.Compare(targetOutput, curOutput)
 		if compRes < 0 {
 			upperbound = curEstimate
@@ -157,11 +172,6 @@ func BinarySearch(f func(input sdk.Int) (sdk.Int, error),
 		} else {
 			return curEstimate, nil
 		}
-		curEstimate = lowerbound.Add(upperbound).QuoRaw(2)
-		curOutput, err = f(curEstimate)
-		if err != nil {
-			return sdk.Int{}, err
-		}
 	}
 
 	return sdk.Int{}, errors.New("hit maximum iterations, did not converge fast enough")
@@ -182,21 +192,22 @@ type SdkDec[D any] interface {
 //
 // It binary searches on the input range, until it finds an input y s.t. f(y) meets the err tolerance constraints for how close it is to x.
 // If we perform more than maxIterations (or equivalently lowerbound = upperbound), we return an error.
-func BinarySearchBigDec(f func(input BigDec) (BigDec, error),
+func BinarySearchBigDec(f func(input BigDec) BigDec,
 	lowerbound BigDec,
 	upperbound BigDec,
 	targetOutput BigDec,
 	errTolerance ErrTolerance,
 	maxIterations int,
 ) (BigDec, error) {
-	// Setup base case of loop
-	curEstimate := lowerbound.Add(upperbound).Quo(NewBigDec(2))
-	curOutput, err := f(curEstimate)
-	if err != nil {
-		return BigDec{}, err
-	}
+	var (
+		curEstimate, curOutput BigDec
+	)
+
 	curIteration := 0
 	for ; curIteration < maxIterations; curIteration += 1 {
+		curEstimate = lowerbound.Add(upperbound).Quo(NewBigDec(2))
+		curOutput = f(curEstimate)
+
 		// fmt.Println("binary search, input, target output, cur output", curEstimate, targetOutput, curOutput)
 		compRes := errTolerance.CompareBigDec(targetOutput, curOutput)
 		if compRes < 0 {
@@ -206,11 +217,6 @@ func BinarySearchBigDec(f func(input BigDec) (BigDec, error),
 		} else {
 			return curEstimate, nil
 		}
-		curEstimate = lowerbound.Add(upperbound).Quo(NewBigDec(2))
-		curOutput, err = f(curEstimate)
-		if err != nil {
-			return BigDec{}, err
-		}
 	}
 
 	return BigDec{}, errors.New("hit maximum iterations, did not converge fast enough")
diff --git a/x/gamm/pool-models/stableswap/README.md b/x/gamm/pool-models/stableswap/README.md
index f587e2376..43e33f030 100644
--- a/x/gamm/pool-models/stableswap/README.md
+++ b/x/gamm/pool-models/stableswap/README.md
@@ -287,7 +287,7 @@ def CalcOutAmountGivenExactAmountIn(pool, in_coin, out_denom, swap_fee):
   in_reserve, out_reserve, rem_reserves = pool.ScaledLiquidity(in_coin, out_denom, RoundingMode.RoundDown)
   in_amt_scaled = pool.ScaleToken(in_coin, RoundingMode.RoundDown)
   amm_in = in_amt_scaled * (1 - swap_fee)
-  out_amt_scaled = solve_y(in_reserve, out_reserve, remReserves, in_amt_scaled)
+  out_amt_scaled = solve_y(in_reserve, out_reserve, remReserves, amm_in)
   out_amt = pool.DescaleToken(out_amt_scaled, out_denom)
   return out_amt
 ```
@@ -308,7 +308,7 @@ We do this by having `token_in = amm_in / (1 - swapfee)`.
 ```python
 def CalcInAmountGivenExactAmountOut(pool, out_coin, in_denom, swap_fee):
   in_reserve, out_reserve, rem_reserves = pool.ScaledLiquidity(in_denom, out_coin, RoundingMode.RoundDown)
-  out_amt_scaled = pool.ScaleToken(in_coin, RoundingMode.RoundUp)
+  out_amt_scaled = pool.ScaleToken(out_coin, RoundingMode.RoundUp)
 
   amm_in_scaled = solve_y(out_reserve, in_reserve, remReserves, -out_amt_scaled)
   swap_in_scaled = ceil(amm_in_scaled / (1 - swapfee))
diff --git a/x/gamm/pool-models/stableswap/amm.go b/x/gamm/pool-models/stableswap/amm.go
index bd5683ae9..b232cb27f 100644
--- a/x/gamm/pool-models/stableswap/amm.go
+++ b/x/gamm/pool-models/stableswap/amm.go
@@ -38,10 +38,6 @@ func cfmmConstant(xReserve, yReserve osmomath.BigDec) osmomath.BigDec {
 // We use this version for calculations since the u
 // term in the full CFMM is constant.
 func cfmmConstantMultiNoV(xReserve, yReserve, wSumSquares osmomath.BigDec) osmomath.BigDec {
-	if !xReserve.IsPositive() || !yReserve.IsPositive() || wSumSquares.IsNegative() {
-		panic("invalid input: reserves must be positive")
-	}
-
 	return cfmmConstantMultiNoVY(xReserve, yReserve, wSumSquares).Mul(yReserve)
 }
 
@@ -263,19 +259,19 @@ func targetKCalculator(x0, y0, w, yf osmomath.BigDec) osmomath.BigDec {
 
 // $$k_{iter}(x_f) = -x_{out}^3 + 3 x_0 x_{out}^2 - (y_f^2 + w + 3x_0^2)x_{out}$$
 // where x_out = x_0 - x_f
-func iterKCalculator(x0, w, yf osmomath.BigDec) func(osmomath.BigDec) (osmomath.BigDec, error) {
+func iterKCalculator(x0, w, yf osmomath.BigDec) func(osmomath.BigDec) osmomath.BigDec {
 	// compute coefficients first
 	cubicCoeff := osmomath.OneDec().Neg()
 	quadraticCoeff := x0.MulInt64(3)
 	linearCoeff := quadraticCoeff.Mul(x0).Add(w).Add(yf.Mul(yf)).Neg()
-	return func(xf osmomath.BigDec) (osmomath.BigDec, error) {
+	return func(xf osmomath.BigDec) osmomath.BigDec {
 		xOut := x0.Sub(xf)
 		// horners method
 		// ax^3 + bx^2 + cx = x(c + x(b + ax))
 		res := cubicCoeff.Mul(xOut)
 		res = res.Add(quadraticCoeff).Mul(xOut)
 		res = res.Add(linearCoeff).Mul(xOut)
-		return res, nil
+		return res
 	}
 }
 
@@ -488,6 +484,7 @@ func (p *Pool) joinPoolSharesInternal(ctx sdk.Context, tokensIn sdk.Coins, swapF
 	if !tokensIn.DenomsSubsetOf(p.GetTotalPoolLiquidity(ctx)) {
 		return sdk.ZeroInt(), sdk.NewCoins(), errors.New("attempted joining pool with assets that do not exist in pool")
 	}
+
 	if len(tokensIn) == 1 && tokensIn[0].Amount.GT(sdk.OneInt()) {
 		numShares, err = p.calcSingleAssetJoinShares(tokensIn[0], swapFee)
 		if err != nil {
@@ -495,27 +492,21 @@ func (p *Pool) joinPoolSharesInternal(ctx sdk.Context, tokensIn sdk.Coins, swapF
 		}
 
 		tokensJoined = tokensIn
-
-		p.updatePoolForJoin(tokensJoined, numShares)
-
-		if err = validatePoolLiquidity(p.PoolLiquidity, p.ScalingFactors); err != nil {
-			return sdk.ZeroInt(), sdk.NewCoins(), err
-		}
-
-		return numShares, tokensJoined, nil
 	} else if len(tokensIn) != p.NumAssets() {
 		return sdk.ZeroInt(), sdk.NewCoins(), errors.New(
 			"stableswap pool only supports LP'ing with one asset, or all assets in pool")
-	}
+	} else {
+		// Add all exact coins we can (no swap). ctx arg doesn't matter for Stableswap
+		var remCoins sdk.Coins
+		numShares, remCoins, err = cfmm_common.MaximalExactRatioJoin(p, sdk.Context{}, tokensIn)
+		if err != nil {
+			return sdk.ZeroInt(), sdk.NewCoins(), err
+		}
 
-	// Add all exact coins we can (no swap). ctx arg doesn't matter for Stableswap
-	numShares, remCoins, err := cfmm_common.MaximalExactRatioJoin(p, sdk.Context{}, tokensIn)
-	if err != nil {
-		return sdk.ZeroInt(), sdk.NewCoins(), err
+		tokensJoined = tokensIn.Sub(remCoins)
 	}
-	p.updatePoolForJoin(tokensIn.Sub(remCoins), numShares)
 
-	tokensJoined = tokensIn.Sub(remCoins)
+	p.updatePoolForJoin(tokensJoined, numShares)
 
 	if err = validatePoolLiquidity(p.PoolLiquidity, p.ScalingFactors); err != nil {
 		return sdk.ZeroInt(), sdk.NewCoins(), err
diff --git a/x/gamm/pool-models/stableswap/pool.go b/x/gamm/pool-models/stableswap/pool.go
index 60c81ebb3..b162a2623 100644
--- a/x/gamm/pool-models/stableswap/pool.go
+++ b/x/gamm/pool-models/stableswap/pool.go
@@ -15,24 +15,6 @@ import (
 
 var _ types.PoolI = &Pool{}
 
-type unsortedPoolLiqError struct {
-	ActualLiquidity sdk.Coins
-}
-
-func (e unsortedPoolLiqError) Error() string {
-	return fmt.Sprintf(`unsorted initial pool liquidity: %s. 
-	Please sort and make sure scaling factor order matches initial liquidity coin order`, e.ActualLiquidity)
-}
-
-type liquidityAndScalingFactorCountMismatchError struct {
-	LiquidityCount     int
-	ScalingFactorCount int
-}
-
-func (e liquidityAndScalingFactorCountMismatchError) Error() string {
-	return fmt.Sprintf("liquidity count (%d) must match scaling factor count (%d)", e.LiquidityCount, e.ScalingFactorCount)
-}
-
 // NewStableswapPool returns a stableswap pool
 // Invariants that are assumed to be satisfied and not checked:
 // * poolID doesn't already exist
@@ -48,17 +30,20 @@ func NewStableswapPool(poolId uint64,
 		}
 	}
 
-	scalingFactors = applyScalingFactorMultiplier(scalingFactors)
+	scalingFactors, err := applyScalingFactorMultiplier(scalingFactors)
+	if err != nil {
+		return Pool{}, err
+	}
 
-	if err := validateScalingFactors(scalingFactors, len(initialLiquidity)); err != nil {
+	if err = validateScalingFactors(scalingFactors, len(initialLiquidity)); err != nil {
 		return Pool{}, err
 	}
 
-	if err := validatePoolLiquidity(initialLiquidity, scalingFactors); err != nil {
+	if err = validatePoolLiquidity(initialLiquidity, scalingFactors); err != nil {
 		return Pool{}, err
 	}
 
-	if err := types.ValidateFutureGovernor(futureGovernor); err != nil {
+	if err = types.ValidateFutureGovernor(futureGovernor); err != nil {
 		return Pool{}, err
 	}
 
@@ -122,8 +107,14 @@ func (p Pool) GetScalingFactors() []uint64 {
 }
 
 // CONTRACT: scaling factors follow the same index with pool liquidity denoms
-func (p Pool) GetScalingFactorByLiquidityIndex(liquidityIndex int) uint64 {
-	return p.ScalingFactors[liquidityIndex]
+func (p Pool) GetScalingFactorByDenom(denom string) uint64 {
+	for i, coin := range p.PoolLiquidity {
+		if denom == coin.Denom {
+			return p.ScalingFactors[i]
+		}
+	}
+
+	return 0
 }
 
 func (p Pool) NumAssets() int {
@@ -133,8 +124,7 @@ func (p Pool) NumAssets() int {
 // scaleCoin returns the BigDec amount of the
 // input token after scaling it by the token's scaling factor
 func (p Pool) scaleCoin(input sdk.Coin, roundingDirection osmomath.RoundingDirection) (osmomath.BigDec, error) {
-	liquidityIndexes := p.getLiquidityIndexMap()
-	scalingFactor := p.GetScalingFactorByLiquidityIndex(liquidityIndexes[input.Denom])
+	scalingFactor := p.GetScalingFactorByDenom(input.Denom)
 	scaledAmount, err := osmomath.DivIntByU64ToBigDec(input.Amount, scalingFactor, roundingDirection)
 	if err != nil {
 		return osmomath.BigDec{}, err
@@ -145,25 +135,11 @@ func (p Pool) scaleCoin(input sdk.Coin, roundingDirection osmomath.RoundingDirec
 // getDescaledPoolAmt descales the passed in amount
 // by the scaling factor of the passed in denom
 func (p Pool) getDescaledPoolAmt(denom string, amount osmomath.BigDec) sdk.Dec {
-	liquidityIndexes := p.getLiquidityIndexMap()
-	liquidityIndex := liquidityIndexes[denom]
-
-	scalingFactor := p.GetScalingFactorByLiquidityIndex(liquidityIndex)
+	scalingFactor := p.GetScalingFactorByDenom(denom)
 
 	return amount.MulInt64(int64(scalingFactor)).SDKDec()
 }
 
-// getLiquidityIndexMap creates a map of denoms to its index in pool liquidity.
-// As always, the caller must not iterate over the map.
-func (p Pool) getLiquidityIndexMap() map[string]int {
-	poolLiquidity := p.PoolLiquidity
-	liquidityIndexMap := make(map[string]int, poolLiquidity.Len())
-	for i, coin := range poolLiquidity {
-		liquidityIndexMap[coin.Denom] = i
-	}
-	return liquidityIndexMap
-}
-
 // scaledSortedPoolReserves sorts and scales passed in pool reserves such that the denom
 // `first` and the denom `second` are ordered first and second,
 // respectively. The rest of the ordering is not specified but
@@ -414,13 +390,16 @@ func (p *Pool) SetScalingFactors(ctx sdk.Context, scalingFactors []uint64, sende
 		return types.ErrNotScalingFactorGovernor
 	}
 
-	scalingFactors = applyScalingFactorMultiplier(scalingFactors)
+	scalingFactors, err := applyScalingFactorMultiplier(scalingFactors)
+	if err != nil {
+		return err
+	}
 
-	if err := validateScalingFactors(scalingFactors, p.PoolLiquidity.Len()); err != nil {
+	if err = validateScalingFactors(scalingFactors, p.PoolLiquidity.Len()); err != nil {
 		return err
 	}
 
-	if err := validatePoolLiquidity(p.PoolLiquidity, scalingFactors); err != nil {
+	if err = validatePoolLiquidity(p.PoolLiquidity, scalingFactors); err != nil {
 		return err
 	}
 
@@ -442,7 +421,7 @@ func validateScalingFactors(scalingFactors []uint64, numAssets int) error {
 	}
 
 	for _, scalingFactor := range scalingFactors {
-		if scalingFactor == 0 || int64(scalingFactor) <= 0 {
+		if int64(scalingFactor) <= 0 {
 			return types.ErrInvalidScalingFactors
 		}
 	}
@@ -455,7 +434,7 @@ func validatePoolLiquidity(liquidity sdk.Coins, scalingFactors []uint64) error {
 	liquidityCount := len(liquidity)
 	scalingFactorCount := len(scalingFactors)
 	if liquidityCount != scalingFactorCount {
-		return liquidityAndScalingFactorCountMismatchError{LiquidityCount: liquidityCount, ScalingFactorCount: scalingFactorCount}
+		return types.LiquidityAndScalingFactorCountMismatchError{LiquidityCount: liquidityCount, ScalingFactorCount: scalingFactorCount}
 	}
 
 	if liquidityCount < types.MinPoolAssets {
@@ -470,7 +449,7 @@ func validatePoolLiquidity(liquidity sdk.Coins, scalingFactors []uint64) error {
 
 	for i, asset := range liquidity {
 		if asset != liquidityCopy[i] {
-			return unsortedPoolLiqError{ActualLiquidity: liquidity}
+			return types.UnsortedPoolLiqError{ActualLiquidity: liquidity}
 		}
 
 		scaledAmount := asset.Amount.Quo(sdk.NewInt(int64(scalingFactors[i])))
@@ -484,11 +463,15 @@ func validatePoolLiquidity(liquidity sdk.Coins, scalingFactors []uint64) error {
 	return nil
 }
 
-func applyScalingFactorMultiplier(scalingFactors []uint64) []uint64 {
+func applyScalingFactorMultiplier(scalingFactors []uint64) ([]uint64, error) {
 	newScalingFactors := make([]uint64, len(scalingFactors))
-	for i := range scalingFactors {
-		newScalingFactors[i] = scalingFactors[i] * types.ScalingFactorMultiplier
+	for i, scalingFactor := range scalingFactors {
+		newScalingFactors[i] = scalingFactor * types.ScalingFactorMultiplier
+
+		if newScalingFactors[i] < scalingFactor {
+			return nil, types.ErrInvalidScalingFactors
+		}
 	}
 
-	return newScalingFactors
+	return newScalingFactors, nil
 }
diff --git a/x/gamm/types/errors.go b/x/gamm/types/errors.go
index 5457f3ede..21fff4d2d 100644
--- a/x/gamm/types/errors.go
+++ b/x/gamm/types/errors.go
@@ -3,6 +3,7 @@ package types
 import (
 	fmt "fmt"
 
+	sdk "github.com/cosmos/cosmos-sdk/types"
 	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
 )
 
@@ -14,6 +15,24 @@ func (e PoolDoesNotExistError) Error() string {
 	return fmt.Sprintf("pool with ID %d does not exist", e.PoolId)
 }
 
+type UnsortedPoolLiqError struct {
+	ActualLiquidity sdk.Coins
+}
+
+func (e UnsortedPoolLiqError) Error() string {
+	return fmt.Sprintf(`unsorted initial pool liquidity: %s. 
+	Please sort and make sure scaling factor order matches initial liquidity coin order`, e.ActualLiquidity)
+}
+
+type LiquidityAndScalingFactorCountMismatchError struct {
+	LiquidityCount     int
+	ScalingFactorCount int
+}
+
+func (e LiquidityAndScalingFactorCountMismatchError) Error() string {
+	return fmt.Sprintf("liquidity count (%d) must match scaling factor count (%d)", e.LiquidityCount, e.ScalingFactorCount)
+}
+
 // x/gamm module sentinel errors.
 var (
 	ErrPoolNotFound        = sdkerrors.Register(ModuleName, 1, "pool not found")


Test patch:
diff --git a/osmomath/binary_search_test.go b/osmomath/binary_search_test.go
index 88b3a9168..e98de7779 100644
--- a/osmomath/binary_search_test.go
+++ b/osmomath/binary_search_test.go
@@ -76,11 +76,11 @@ func TestBinarySearch(t *testing.T) {
 
 // straight line function that returns input. Simplest to binary search on,
 // binary search directly reveals one bit of the answer in each iteration with this function.
-func lineF(a BigDec) (BigDec, error) {
-	return a, nil
+func lineF(a BigDec) BigDec {
+	return a
 }
-func cubicF(a BigDec) (BigDec, error) {
-	return a.PowerInteger(3), nil
+func cubicF(a BigDec) BigDec {
+	return a.PowerInteger(3)
 }
 
 var negCubicFConstant BigDec
@@ -89,11 +89,11 @@ func init() {
 	negCubicFConstant = NewBigDec(1 << 62).PowerInteger(3).Neg()
 }
 
-func negCubicF(a BigDec) (BigDec, error) {
-	return a.PowerInteger(3).Add(negCubicFConstant), nil
+func negCubicF(a BigDec) BigDec {
+	return a.PowerInteger(3).Add(negCubicFConstant)
 }
 
-type searchFn func(BigDec) (BigDec, error)
+type searchFn func(BigDec) BigDec
 
 type binarySearchTestCase struct {
 	f             searchFn
@@ -163,7 +163,7 @@ func TestIterationDepthRandValue(t *testing.T) {
 		errTolerance ErrTolerance, maxNumIters int, errToleranceName string) {
 		targetF := fnMap[fnName]
 		targetX := int64(rand.Intn(int(upperbound-lowerbound-1))) + lowerbound + 1
-		target, _ := targetF(NewBigDec(targetX))
+		target := targetF(NewBigDec(targetX))
 		testCase := binarySearchTestCase{
 			f:          lineF,
 			lowerbound: NewBigDec(lowerbound), upperbound: NewBigDec(upperbound),
diff --git a/x/gamm/pool-models/stableswap/pool_test.go b/x/gamm/pool-models/stableswap/pool_test.go
index 350ef95b4..d1a56692f 100644
--- a/x/gamm/pool-models/stableswap/pool_test.go
+++ b/x/gamm/pool-models/stableswap/pool_test.go
@@ -64,13 +64,15 @@ var (
 
 // we create a pool struct directly to bypass checks in NewStableswapPool()
 func poolStructFromAssets(assets sdk.Coins, scalingFactors []uint64) Pool {
+	scalingFactors, _ = applyScalingFactorMultiplier(scalingFactors)
+
 	p := Pool{
 		Address:            types.NewPoolAddress(defaultPoolId).String(),
 		Id:                 defaultPoolId,
 		PoolParams:         defaultStableswapPoolParams,
 		TotalShares:        sdk.NewCoin(types.GetPoolShareDenom(defaultPoolId), types.InitPoolSharesSupply),
 		PoolLiquidity:      assets,
-		ScalingFactors:     applyScalingFactorMultiplier(scalingFactors),
+		ScalingFactors:     scalingFactors,
 		FuturePoolGovernor: defaultFutureGovernor,
 	}
 	return p
@@ -241,39 +243,45 @@ func TestScaledSortedPoolReserves(t *testing.T) {
 	}
 }
 
-func TestGetLiquidityIndexMap(t *testing.T) {
+func TestGetScalingFactorByDenom(t *testing.T) {
 	tests := map[string]struct {
-		poolAssets sdk.Coins
+		denom          string
+		poolAssets     sdk.Coins
+		scalingFactors []uint64
+		expResult      uint64
 	}{
-		"2 asset pool": {
-			twoEvenStablePoolAssets,
+		"pass in no denoms": {
+			denom:          "",
+			poolAssets:     twoEvenStablePoolAssets,
+			scalingFactors: defaultTwoAssetScalingFactors,
+			expResult:      0,
 		},
-		"3 asset pool": {
-			threeEvenStablePoolAssets,
+		"get scaling factor for first asset (two-asset pool)": {
+			denom:          "bar",
+			poolAssets:     twoEvenStablePoolAssets,
+			scalingFactors: []uint64{1, 2},
+			expResult:      1,
 		},
-		"4 asset pool": {
-			sdk.NewCoins(
-				sdk.NewInt64Coin("asset/a", 1000000000),
-				sdk.NewInt64Coin("asset/b", 1000000000),
-				sdk.NewInt64Coin("asset/c", 1000000000),
-				sdk.NewInt64Coin("asset/d", 1000000000),
-			),
+		"get scaling factor for second asset (two-asset pool)": {
+			denom:          "foo",
+			poolAssets:     twoEvenStablePoolAssets,
+			scalingFactors: []uint64{1, 2},
+			expResult:      2,
 		},
-		"5 asset pool": {
-			fiveEvenStablePoolAssets,
+		"get scaling factor for second asset (three-asset pool)": {
+			denom:          "asset/b",
+			poolAssets:     threeEvenStablePoolAssets,
+			scalingFactors: []uint64{1, 2, 3},
+			expResult:      2,
 		},
 	}
 
 	for name, tc := range tests {
 		t.Run(name, func(t *testing.T) {
-			pool := poolStructFromAssets(tc.poolAssets, []uint64{})
+			p := poolStructFromAssets(tc.poolAssets, tc.scalingFactors)
 
-			indexMap := pool.getLiquidityIndexMap()
-			require.Equal(t, len(indexMap), len(tc.poolAssets))
-			for ind := 0; ind < len(tc.poolAssets); ind++ {
-				actual_ind := indexMap[tc.poolAssets[ind].Denom]
-				require.Equal(t, actual_ind, ind)
-			}
+			factor := p.GetScalingFactorByDenom(tc.denom)
+			require.Equal(t, tc.expResult, factor)
 		})
 	}
 }
@@ -1082,7 +1090,7 @@ func TestValidatePoolLiquidity(t *testing.T) {
 				coinB,
 			},
 			scalingFactors: []uint64{10, 10, 10, 10},
-			expectError: unsortedPoolLiqError{ActualLiquidity: sdk.Coins{
+			expectError: types.UnsortedPoolLiqError{ActualLiquidity: sdk.Coins{
 				coinD,
 				coinA,
 				coinC,
@@ -1097,7 +1105,7 @@ func TestValidatePoolLiquidity(t *testing.T) {
 				coinD,
 			},
 			scalingFactors: []uint64{10, 10},
-			expectError: liquidityAndScalingFactorCountMismatchError{
+			expectError: types.LiquidityAndScalingFactorCountMismatchError{
 				LiquidityCount:     4,
 				ScalingFactorCount: 2,
 			},
diff --git a/x/gamm/pool-models/stableswap/util_test.go b/x/gamm/pool-models/stableswap/util_test.go
index 6c05cce8c..fbc359e30 100644
--- a/x/gamm/pool-models/stableswap/util_test.go
+++ b/x/gamm/pool-models/stableswap/util_test.go
@@ -10,10 +10,12 @@ import (
 )
 
 func createTestPool(t *testing.T, poolLiquidity sdk.Coins, swapFee, exitFee sdk.Dec, scalingFactors []uint64) types.PoolI {
+	scalingFactors, _ = applyScalingFactorMultiplier(scalingFactors)
+
 	pool, err := NewStableswapPool(1, PoolParams{
 		SwapFee: swapFee,
 		ExitFee: exitFee,
-	}, poolLiquidity, applyScalingFactorMultiplier(scalingFactors), "", "")
+	}, poolLiquidity, scalingFactors, "", "")
 
 	require.NoError(t, err)