Repository: osmosis-labs/osmosis
Pull request #4582 (2023-03-12 15:26:47)

Consistently generate build tags metadata
References: cosmos/gaia#2017

## What is the purpose of the change

Dockerfile receives a minor tweak for proper output.

BuildTags value is expected as a plain `netgo,ledger,muslc` string, however the current approach wraps quotes inside of quotes, and the resulting string is `'netgo,ledger,muslc'` which gets escaped a few more times (unnecessarily).

Additionally, Makefile receives a minor tweak for everyone who builds directly.

With Make 4.3+, the empty whitespace does not seem to work as originally intended. This causes build tags to be `netgo ledger,` on Ubuntu 22.04 and other systems that include the newer Make version. The build tags were intended as `netgo,ledger` which can be observed on Make 4.2 (shipped with Ubuntu 20.04).

This change swaps out the `+=` use in favor of an explicit `:=`. [_Reference_](https://www.gnu.org/software/make/manual/html_node/Appending.html)

## Brief Changelog

  - Consistently generate build tags metadata, to return a comma-separated list without stray quotes. This affects the output from `version` CLI subcommand and server info API calls.

## Testing and Verifying

This change is a trivial rework / code cleanup without any test coverage.

The difference in `build_tags` output can be observed:

```bash
# pre-built (v14.0.1)
$ osmosisd-14.0.1-linux-amd64 version --long 2>&1 | head -n5
name: osmosis
server_name: osmosisd
version: 14.0.1
commit: 0a4b5bed878af2ea2167749f937e7cdde21f9031
build_tags: '''netgo,ledger,muslc'''
```

```bash
# `docker build -t osmosis .` (PR branch)
$ docker run --rm -it osmosis version --long | head -n5
name: osmosis
server_name: osmosisd
version: ""
commit: ""
build_tags: netgo,ledger,muslc
```

## Documentation and Release Note

  - Does this pull request introduce a new feature or user-facing behavior changes? (**yes**) If someone is actually parsing these programmatically, then it's a user-facing behavior changes.
  - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? (**yes**)
  - How is the feature or change documented? (**not applicable**) The behavior change is implementing the originally intended BuildTags behavior by Cosmoshub/Gaia and all derivatives. A changelog warning is in place, but further documentation is not beneficial here.


Patch:
diff --git a/.dockerignore b/.dockerignore
index 0cd5df6b1..14ef7bb17 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -9,3 +9,5 @@ tests/cl-go-client
 .github/
 .git/
 .vscode/
+go.work
+go.work.sum
diff --git a/.github/workflows/gen-upgrade-on-manual-input.yml b/.github/workflows/gen-upgrade-on-manual-input.yml
index 8a6cfaf57..91a89daba 100644
--- a/.github/workflows/gen-upgrade-on-manual-input.yml
+++ b/.github/workflows/gen-upgrade-on-manual-input.yml
@@ -27,6 +27,11 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v2.3.4
 
+      - name: Setup Go
+        uses: actions/setup-go@v3
+        with:
+          go-version: '>=1.19.0'
+          
       - name: Specify input
         run: |
           env=${{ inputs.version }}
@@ -49,6 +54,11 @@ jobs:
         if: env.MAJOR == 1 # 1 means vX of existing upgrade handler is smaller than A in tag vA.B.C
         run: bash ./scripts/empty_upgrade_handler_gen.sh ${{ env.input }}
 
+      - name: Format app/upgrades
+        run: |
+          cd app/upgrades
+          go fmt ./...
+
       - name: Create PR
         if: env.MAJOR == 1
         uses: peter-evans/create-pull-request@v4
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1fe6ad487..4f29b283b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,12 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [Unreleased]
+
+### Misc Improvements
+
+  * [#4582](https://github.com/osmosis-labs/osmosis/pull/4582) Consistently generate build tags metadata, to return a comma-separated list without stray quotes. This affects the output from `version` CLI subcommand and server info API calls.
+
 ## v15.0.0
 
 This release containts the following new modules:
diff --git a/Dockerfile b/Dockerfile
index e2de25d8c..eb0b2c575 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -46,7 +46,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
             -X github.com/cosmos/cosmos-sdk/version.AppName="osmosisd" \
             -X github.com/cosmos/cosmos-sdk/version.Version=${GIT_VERSION} \
             -X github.com/cosmos/cosmos-sdk/version.Commit=${GIT_COMMIT} \
-            -X github.com/cosmos/cosmos-sdk/version.BuildTags='netgo,ledger,muslc' \
+            -X github.com/cosmos/cosmos-sdk/version.BuildTags=netgo,ledger,muslc \
             -w -s -linkmode=external -extldflags '-Wl,-z,muldefs -static'" \
         -trimpath \
         -o /osmosis/build/osmosisd \
diff --git a/Makefile b/Makefile
index 933e2b33c..d8d13d86b 100644
--- a/Makefile
+++ b/Makefile
@@ -50,7 +50,7 @@ build_tags += $(BUILD_TAGS)
 build_tags := $(strip $(build_tags))
 
 whitespace :=
-whitespace += $(whitespace)
+whitespace := $(whitespace) $(whitespace)
 comma := ,
 build_tags_comma_sep := $(subst $(whitespace),$(comma),$(build_tags))
 
diff --git a/go.mod b/go.mod
index 153dc1633..f659c5b28 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
 	github.com/cosmos/ibc-go/v4 v4.3.0
 	github.com/gogo/protobuf v1.3.3
 	github.com/golang/mock v1.6.0
-	github.com/golang/protobuf v1.5.2
+	github.com/golang/protobuf v1.5.3
 	github.com/golangci/golangci-lint v1.51.2
 	github.com/gorilla/mux v1.8.0
 	github.com/grpc-ecosystem/grpc-gateway v1.16.0
@@ -34,7 +34,7 @@ require (
 	github.com/tendermint/tendermint v0.34.24
 	github.com/tendermint/tm-db v0.6.8-0.20220506192307-f628bb5dc95b
 	github.com/tidwall/btree v1.6.0
-	go.uber.org/multierr v1.9.0
+	go.uber.org/multierr v1.10.0
 	golang.org/x/exp v0.0.0-20221212164502-fae10dda9338
 	google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef
 	google.golang.org/grpc v1.52.0
@@ -194,7 +194,7 @@ require (
 	github.com/jingyugao/rowserrcheck v1.1.1 // indirect
 	github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
 	github.com/jmhodges/levigo v1.0.0 // indirect
-	github.com/joho/godotenv v1.4.0
+	github.com/joho/godotenv v1.5.1
 	github.com/julz/importas v0.1.0 // indirect
 	github.com/kisielk/errcheck v1.6.3 // indirect
 	github.com/kisielk/gotool v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 4838824f1..fddf658da 100644
--- a/go.sum
+++ b/go.sum
@@ -481,8 +481,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
@@ -694,8 +695,8 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
 github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
-github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
-github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
@@ -1282,8 +1283,8 @@ go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
 go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
-go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
-go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
diff --git a/x/poolmanager/router.go b/x/poolmanager/router.go
index 5c9a748f3..bde509c1e 100644
--- a/x/poolmanager/router.go
+++ b/x/poolmanager/router.go
@@ -305,6 +305,42 @@ func (k Keeper) RouteExactAmountOut(ctx sdk.Context,
 	return tokenInAmount, nil
 }
 
+func (k Keeper) RouteGetPoolDenoms(
+	ctx sdk.Context,
+	poolId uint64,
+) (denoms []string, err error) {
+	swapModule, err := k.GetPoolModule(ctx, poolId)
+	if err != nil {
+		return []string{}, err
+	}
+
+	denoms, err = swapModule.GetPoolDenoms(ctx, poolId)
+	if err != nil {
+		return []string{}, err
+	}
+
+	return denoms, nil
+}
+
+func (k Keeper) RouteCalculateSpotPrice(
+	ctx sdk.Context,
+	poolId uint64,
+	quoteAssetDenom string,
+	baseAssetDenom string,
+) (price sdk.Dec, err error) {
+	swapModule, err := k.GetPoolModule(ctx, poolId)
+	if err != nil {
+		return sdk.Dec{}, err
+	}
+
+	price, err = swapModule.CalculateSpotPrice(ctx, poolId, quoteAssetDenom, baseAssetDenom)
+	if err != nil {
+		return sdk.Dec{}, err
+	}
+
+	return price, nil
+}
+
 func (k Keeper) MultihopEstimateInGivenExactAmountOut(
 	ctx sdk.Context,
 	routes []types.SwapAmountOutRoute,
diff --git a/x/poolmanager/types/routes.go b/x/poolmanager/types/routes.go
index b4a66b715..7c542c814 100644
--- a/x/poolmanager/types/routes.go
+++ b/x/poolmanager/types/routes.go
@@ -33,6 +33,14 @@ type SwapI interface {
 
 	GetPool(ctx sdk.Context, poolId uint64) (PoolI, error)
 
+	GetPoolDenoms(ctx sdk.Context, poolId uint64) (denoms []string, err error)
+	CalculateSpotPrice(
+		ctx sdk.Context,
+		poolId uint64,
+		quoteAssetDenom string,
+		baseAssetDenom string,
+	) (price sdk.Dec, err error)
+
 	SwapExactAmountIn(
 		ctx sdk.Context,
 		sender sdk.AccAddress,


Test patch:
diff --git a/app/apptesting/concentrated_liquidity.go b/app/apptesting/concentrated_liquidity.go
index 10b4819c3..26b2e3d5e 100644
--- a/app/apptesting/concentrated_liquidity.go
+++ b/app/apptesting/concentrated_liquidity.go
@@ -20,6 +20,10 @@ func (s *KeeperTestHelper) PrepareConcentratedPool() types.ConcentratedPoolExten
 	return s.PrepareCustomConcentratedPool(s.TestAccs[0], ETH, USDC, DefaultTickSpacing, DefaultExponentAtPriceOne, sdk.ZeroDec())
 }
 
+func (s *KeeperTestHelper) PrepareConcentratedPoolWithCoins(denom1, denom2 string) types.ConcentratedPoolExtension {
+	return s.PrepareCustomConcentratedPool(s.TestAccs[0], denom1, denom2, DefaultTickSpacing, DefaultExponentAtPriceOne, sdk.ZeroDec())
+}
+
 // PrepareCustomConcentratedPool sets up a concentrated liquidity pool with the custom parameters.
 func (s *KeeperTestHelper) PrepareCustomConcentratedPool(owner sdk.AccAddress, denom0, denom1 string, tickSpacing uint64, exponentAtPriceOne sdk.Int, swapFee sdk.Dec) types.ConcentratedPoolExtension {
 	// Mint some assets to the account.
diff --git a/tests/e2e/configurer/chain/commands.go b/tests/e2e/configurer/chain/commands.go
index b579232fc..5339e0ca8 100644
--- a/tests/e2e/configurer/chain/commands.go
+++ b/tests/e2e/configurer/chain/commands.go
@@ -47,6 +47,17 @@ func (n *NodeConfig) CreateBalancerPool(poolFile, from string) uint64 {
 	return poolID
 }
 
+func (n *NodeConfig) CreateStableswapPool(poolFile, from string) uint64 {
+	n.LogActionF("creating stableswap pool from file %s", poolFile)
+	cmd := []string{"osmosisd", "tx", "gamm", "create-pool", fmt.Sprintf("--pool-file=/osmosis/%s", poolFile), "--pool-type=stableswap", fmt.Sprintf("--from=%s", from)}
+	_, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd)
+	require.NoError(n.t, err)
+
+	poolID := n.QueryNumPools()
+	n.LogActionF("successfully created stableswap pool with ID %d", poolID)
+	return poolID
+}
+
 func (n *NodeConfig) CreateConcentratedPool(from, denom1, denom2 string, tickSpacing uint64, exponentAtPriceOne int64, swapFee string) uint64 {
 	n.LogActionF("creating concentrated pool")
 
diff --git a/tests/e2e/configurer/config/constants.go b/tests/e2e/configurer/config/constants.go
index b37e1c2be..a0d458534 100644
--- a/tests/e2e/configurer/config/constants.go
+++ b/tests/e2e/configurer/config/constants.go
@@ -28,13 +28,15 @@ var (
 	InitialMinExpeditedDeposit = MinExpeditedDepositValue / 4
 	// The first id of a pool create via CLI before starting an
 	// upgrade.
-	// Note: that we create a pool with id 1 via genesis
-	// in the initialization package. As a result, the first
-	// pre-upgrade should have id 2.
-	// This value gets mutated during the pre-upgrade pool
-	// creation in case more pools are added to genesis
-	// in the future
-	PreUpgradePoolId uint64 = 2
+	PreUpgradePoolId uint64
+
+	PreUpgradeStableSwapPoolId uint64
 
 	StrideMigrateWallet = "stride-migration"
+
+	LockupWallet = "lockup-wallet"
+
+	LockupWalletSuperfluid = "lockup-wallet-superfluid"
+
+	StableswapWallet = "stableswap-wallet"
 )
diff --git a/tests/e2e/configurer/upgrade.go b/tests/e2e/configurer/upgrade.go
index 58026906b..3cad88144 100644
--- a/tests/e2e/configurer/upgrade.go
+++ b/tests/e2e/configurer/upgrade.go
@@ -106,9 +106,6 @@ func (uc *UpgradeConfigurer) ConfigureChain(chainConfig *chain.Config) error {
 }
 
 func (uc *UpgradeConfigurer) CreatePreUpgradeState() error {
-	const lockupWallet = "lockup-wallet"
-	const lockupWalletSuperfluid = "lockup-wallet-superfluid"
-
 	chainA := uc.chainConfigs[0]
 	chainANode, err := chainA.GetDefaultNode()
 	if err != nil {
@@ -128,14 +125,25 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error {
 	config.PreUpgradePoolId = chainANode.CreateBalancerPool("pool1A.json", initialization.ValidatorWalletName)
 	poolShareDenom := fmt.Sprintf("gamm/pool/%d", config.PreUpgradePoolId)
 	chainBNode.CreateBalancerPool("pool1B.json", initialization.ValidatorWalletName)
+	config.PreUpgradeStableSwapPoolId = chainANode.CreateStableswapPool("stablePool.json", initialization.ValidatorWalletName)
+	chainBNode.CreateStableswapPool("stablePool.json", initialization.ValidatorWalletName)
 
 	// enable superfluid assets on chainA
 	chainA.EnableSuperfluidAsset(poolShareDenom)
 
-	// setup wallets and send gamm tokens to these wallets (only chainA)
-	lockupWalletAddrA, lockupWalletSuperfluidAddrA := chainANode.CreateWallet(lockupWallet), chainANode.CreateWallet(lockupWalletSuperfluid)
-	chainANode.BankSend("10000000000000000000"+poolShareDenom, chainA.NodeConfigs[0].PublicAddress, lockupWalletAddrA)
-	chainANode.BankSend("10000000000000000000"+poolShareDenom, chainA.NodeConfigs[0].PublicAddress, lockupWalletSuperfluidAddrA)
+	// Setup wallets and send tokens to wallets (only chainA)
+	config.LockupWallet = chainANode.CreateWalletAndFund(config.LockupWallet, []string{
+		"10000000000000000000" + poolShareDenom,
+	})
+	config.LockupWalletSuperfluid = chainANode.CreateWalletAndFund(config.LockupWalletSuperfluid, []string{
+		"10000000000000000000" + poolShareDenom,
+	})
+	config.StableswapWallet = chainANode.CreateWalletAndFund(config.LockupWalletSuperfluid, []string{
+		"100000stake",
+	})
+
+	// test swap exact amount in for stable swap pool (only chainA)A
+	chainANode.SwapExactAmountIn("2000stake", "1", fmt.Sprintf("%d", config.PreUpgradeStableSwapPoolId), "uosmo", config.StableswapWallet)
 
 	// Upload the rate limiting contract to both chains (as they both will be updated)
 	uc.t.Logf("Uploading rate limiting contract to both chains")
@@ -149,7 +157,7 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error {
 	}
 
 	// test lock and add to existing lock for both regular and superfluid lockups (only chainA)
-	chainA.LockAndAddToExistingLock(sdk.NewInt(1000000000000000000), poolShareDenom, lockupWalletAddrA, lockupWalletSuperfluidAddrA)
+	chainA.LockAndAddToExistingLock(sdk.NewInt(1000000000000000000), poolShareDenom, config.LockupWallet, config.LockupWalletSuperfluid)
 
 	// LP to pools 833, 817, 810
 	// initialize lp wallets
diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go
index e2cec58ec..25fe65b9d 100644
--- a/tests/e2e/e2e_test.go
+++ b/tests/e2e/e2e_test.go
@@ -321,6 +321,33 @@ func (s *IntegrationTestSuite) TestConcentratedLiquidity() {
 
 }
 
+func (s *IntegrationTestSuite) TestStableSwapPostUpgrade() {
+	if s.skipUpgrade {
+		s.T().Skip("Skipping StableSwapPostUpgrade test")
+	}
+
+	chainA := s.configurer.GetChainConfig(0)
+	chainANode, err := chainA.GetDefaultNode()
+	s.Require().NoError(err)
+
+	const (
+		denomA = "stake"
+		denomB = "uosmo"
+
+		minAmountOut = "1"
+	)
+
+	coinAIn, coinBIn := fmt.Sprintf("20000%s", denomA), fmt.Sprintf("1%s", denomB)
+
+	chainANode.BankSend(initialization.WalletFeeTokens.String(), chainA.NodeConfigs[0].PublicAddress, config.StableswapWallet)
+	chainANode.BankSend(coinAIn, chainA.NodeConfigs[0].PublicAddress, config.StableswapWallet)
+	chainANode.BankSend(coinBIn, chainA.NodeConfigs[0].PublicAddress, config.StableswapWallet)
+
+	s.T().Log("performing swaps")
+	chainANode.SwapExactAmountIn(coinAIn, minAmountOut, fmt.Sprintf("%d", config.PreUpgradeStableSwapPoolId), denomB, config.StableswapWallet)
+	chainANode.SwapExactAmountIn(coinBIn, minAmountOut, fmt.Sprintf("%d", config.PreUpgradeStableSwapPoolId), denomA, config.StableswapWallet)
+}
+
 // TestGeometricTwapMigration tests that the geometric twap record
 // migration runs succesfully. It does so by attempting to execute
 // the swap on the pool created pre-upgrade. When a pool is created
diff --git a/tests/e2e/scripts/stablePool.json b/tests/e2e/scripts/stablePool.json
new file mode 100644
index 000000000..0f6d3be97
--- /dev/null
+++ b/tests/e2e/scripts/stablePool.json
@@ -0,0 +1,7 @@
+{
+	"initial-deposit": "1000000stake,1000uosmo",
+	"swap-fee": "0.005",
+	"exit-fee": "0",
+	"future-governor": "",
+        "scaling-factors": "1000,1"
+}
\ No newline at end of file
diff --git a/x/poolmanager/keeper_test.go b/x/poolmanager/keeper_test.go
index bc487999f..3f985be54 100644
--- a/x/poolmanager/keeper_test.go
+++ b/x/poolmanager/keeper_test.go
@@ -49,7 +49,7 @@ func (suite *KeeperTestSuite) createPoolFromType(poolType types.PoolType) {
 		suite.PrepareBasicStableswapPool()
 		return
 	case types.Concentrated:
-		// TODO
+		suite.PrepareConcentratedPool()
 		return
 	}
 }
diff --git a/x/poolmanager/router_test.go b/x/poolmanager/router_test.go
index 1b53e5cbd..0972a8019 100644
--- a/x/poolmanager/router_test.go
+++ b/x/poolmanager/router_test.go
@@ -2,10 +2,12 @@ package poolmanager_test
 
 import (
 	"reflect"
+	"time"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
 	cl "github.com/osmosis-labs/osmosis/v15/x/concentrated-liquidity"
+	cltypes "github.com/osmosis-labs/osmosis/v15/x/concentrated-liquidity/types"
 	gamm "github.com/osmosis-labs/osmosis/v15/x/gamm/keeper"
 	"github.com/osmosis-labs/osmosis/v15/x/gamm/pool-models/balancer"
 	poolincentivestypes "github.com/osmosis-labs/osmosis/v15/x/pool-incentives/types"
@@ -98,6 +100,181 @@ func (suite *KeeperTestSuite) TestGetPoolModule() {
 	}
 }
 
+func (suite *KeeperTestSuite) TestRouteGetPoolDenoms() {
+	tests := map[string]struct {
+		poolId            uint64
+		preCreatePoolType types.PoolType
+		routesOverwrite   map[types.PoolType]types.SwapI
+
+		expectedDenoms []string
+		expectError    error
+	}{
+		"valid balancer pool": {
+			preCreatePoolType: types.Balancer,
+			poolId:            1,
+			expectedDenoms:    []string{"bar", "baz", "foo", "uosmo"},
+		},
+		"valid stableswap pool": {
+			preCreatePoolType: types.Stableswap,
+			poolId:            1,
+			expectedDenoms:    []string{"bar", "baz", "foo"},
+		},
+		"valid concentrated liquidity pool": {
+			preCreatePoolType: types.Concentrated,
+			poolId:            1,
+			expectedDenoms:    []string{"eth", "usdc"},
+		},
+
+		"non-existent pool": {
+			preCreatePoolType: types.Balancer,
+			poolId:            2,
+
+			expectError: types.FailedToFindRouteError{PoolId: 2},
+		},
+		"undefined route": {
+			preCreatePoolType: types.Balancer,
+			poolId:            1,
+			routesOverwrite: map[types.PoolType]types.SwapI{
+				types.Stableswap: &gamm.Keeper{}, // undefined for balancer.
+			},
+
+			expectError: types.UndefinedRouteError{PoolId: 1, PoolType: types.Balancer},
+		},
+	}
+
+	for name, tc := range tests {
+		tc := tc
+		suite.Run(name, func() {
+			suite.SetupTest()
+			poolmanagerKeeper := suite.App.PoolManagerKeeper
+
+			suite.createPoolFromType(tc.preCreatePoolType)
+
+			if len(tc.routesOverwrite) > 0 {
+				poolmanagerKeeper.SetPoolRoutesUnsafe(tc.routesOverwrite)
+			}
+
+			denoms, err := poolmanagerKeeper.RouteGetPoolDenoms(suite.Ctx, tc.poolId)
+			if tc.expectError != nil {
+				suite.Require().Error(err)
+				suite.Require().ErrorIs(err, tc.expectError)
+				return
+			}
+			suite.Require().NoError(err)
+			suite.Require().Equal(tc.expectedDenoms, denoms)
+		})
+	}
+}
+
+func (suite *KeeperTestSuite) TestRouteCalculateSpotPrice() {
+	tests := map[string]struct {
+		poolId               uint64
+		preCreatePoolType    types.PoolType
+		quoteAssetDenom      string
+		baseAssetDenom       string
+		setPositionForCLPool bool
+
+		routesOverwrite   map[types.PoolType]types.SwapI
+		expectedSpotPrice sdk.Dec
+
+		expectError error
+	}{
+		"valid balancer pool": {
+			preCreatePoolType: types.Balancer,
+			poolId:            1,
+			quoteAssetDenom:   "bar",
+			baseAssetDenom:    "baz",
+			expectedSpotPrice: sdk.MustNewDecFromStr("1.5"),
+		},
+		"valid stableswap pool": {
+			preCreatePoolType: types.Stableswap,
+			poolId:            1,
+			quoteAssetDenom:   "bar",
+			baseAssetDenom:    "baz",
+			expectedSpotPrice: sdk.MustNewDecFromStr("0.99999998"),
+		},
+		"valid concentrated liquidity pool with position": {
+			preCreatePoolType:    types.Concentrated,
+			poolId:               1,
+			quoteAssetDenom:      "eth",
+			baseAssetDenom:       "usdc",
+			setPositionForCLPool: true,
+			expectedSpotPrice:    sdk.MustNewDecFromStr("4999.999999999999999988"),
+		},
+		"valid concentrated liquidity pool without position": {
+			preCreatePoolType: types.Concentrated,
+			poolId:            1,
+			quoteAssetDenom:   "usdc",
+			baseAssetDenom:    "eth",
+
+			expectError: cltypes.PriceBoundError{
+				ProvidedPrice: sdk.ZeroDec(),
+				MinSpotPrice:  cltypes.MinSpotPrice,
+				MaxSpotPrice:  cltypes.MaxSpotPrice,
+			},
+		},
+		"non-existent pool": {
+			preCreatePoolType: types.Balancer,
+			poolId:            2,
+
+			expectError: types.FailedToFindRouteError{PoolId: 2},
+		},
+		"undefined route": {
+			preCreatePoolType: types.Balancer,
+			poolId:            1,
+			routesOverwrite: map[types.PoolType]types.SwapI{
+				types.Stableswap: &gamm.Keeper{}, // undefined for balancer.
+			},
+
+			expectError: types.UndefinedRouteError{PoolId: 1, PoolType: types.Balancer},
+		},
+	}
+
+	for name, tc := range tests {
+		tc := tc
+		suite.Run(name, func() {
+			suite.SetupTest()
+			poolmanagerKeeper := suite.App.PoolManagerKeeper
+
+			suite.createPoolFromType(tc.preCreatePoolType)
+
+			// we manually set position for CL to set spot price to correct value
+			if tc.setPositionForCLPool {
+				coin0 := sdk.NewCoin("eth", sdk.NewInt(1000000))
+				coin1 := sdk.NewCoin("usdc", sdk.NewInt(5000000000))
+				suite.FundAcc(suite.TestAccs[0], sdk.NewCoins(coin0, coin1))
+
+				clMsgServer := cl.NewMsgServerImpl(suite.App.ConcentratedLiquidityKeeper)
+				_, err := clMsgServer.CreatePosition(sdk.WrapSDKContext(suite.Ctx), &cltypes.MsgCreatePosition{
+					PoolId:          1,
+					Sender:          suite.TestAccs[0].String(),
+					LowerTick:       int64(305450),
+					UpperTick:       int64(315000),
+					TokenDesired0:   coin0,
+					TokenDesired1:   coin1,
+					TokenMinAmount0: sdk.ZeroInt(),
+					TokenMinAmount1: sdk.ZeroInt(),
+					FreezeDuration:  time.Duration(time.Hour * 24),
+				})
+				suite.Require().NoError(err)
+			}
+
+			if len(tc.routesOverwrite) > 0 {
+				poolmanagerKeeper.SetPoolRoutesUnsafe(tc.routesOverwrite)
+			}
+
+			spotPrice, err := poolmanagerKeeper.RouteCalculateSpotPrice(suite.Ctx, tc.poolId, tc.quoteAssetDenom, tc.baseAssetDenom)
+			if tc.expectError != nil {
+				suite.Require().Error(err)
+				suite.Require().ErrorContains(err, tc.expectError.Error())
+				return
+			}
+			suite.Require().NoError(err)
+			suite.Require().Equal(tc.expectedSpotPrice, spotPrice)
+		})
+	}
+}
+
 // TestMultihopSwapExactAmountIn tests that the swaps are routed correctly.
 // That is:
 // - to the correct module (concentrated-liquidity or gamm)