Repository: osmosis-labs/osmosis
Pull request #2725 (2022-09-13 21:01:07)

Make gamm pools override addr already exists
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
v                               ✰  Thanks for creating a PR! ✰    
v    Before smashing the submit button please review the checkboxes.
v    If a checkbox is n/a - please still include it but + a little note why
v    If your PR doesn't close an issue, that's OK!  Just remove the Closes: #XXX line!
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >  -->

## What is the purpose of the change

Make gamm pools still creatable if the underlying account already exists

## Brief Changelog

- Add helper methods for creating module accounts, to override underlying ones. This is safe because module accounts as long created via the correct constructors, have domain separation and collision resistance from other addresses.
- Apply change to AMM module

## Testing and Verifying

This change added tests and can be verified as follows:

- added unit tests that validate the new method works with prior addresses existing

## Documentation and Release Note

  - Does this pull request introduce a new feature or user-facing behavior changes? yes
  - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? no*
  - How is the feature or change documented? code comments


Patch:
diff --git a/.gitpod.yml b/.gitpod.yml
index 5d7ad67e7..405d1239b 100644
--- a/.gitpod.yml
+++ b/.gitpod.yml
@@ -1,4 +1,4 @@
-image: ghcr.io/faddat/cosmos
+image: ghcr.io/notional-labs/cosmos
 
 ports:
   - port: 1317
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc8c99b94..91865a7ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -77,6 +77,7 @@ which acts as a fuzz testing tool tailored for the SDK state machine.
 * [#2206](https://github.com/osmosis-labs/osmosis/pull/2283) Register all Amino interfaces and concrete types on the authz Amino codec. This will allow the authz module to properly serialize and de-serializes instances using Amino.
 * [#2405](https://github.com/osmosis-labs/osmosis/pull/2405) Make SpotPrice have a max value of 2^160, and no longer be able to panic
 * [#2473](https://github.com/osmosis-labs/osmosis/pull/2473) x/superfluid `AddNewSuperfluidAsset` now returns error, if any occurs instead of ignoring it.
+* [#2714](https://github.com/osmosis-labs/osmosis/pull/2714) Upgrade wasmd to v0.28.0.
 
 #### Golang API breaks
 
@@ -97,7 +98,6 @@ which acts as a fuzz testing tool tailored for the SDK state machine.
 ### Features
 
 * [#2387](https://github.com/osmosis-labs/osmosis/pull/2387) Upgrade to IBC v3.2.0, which allows for sending/receiving IBC tokens with slashes.
-* [#2057](https://github.com/osmosis-labs/osmosis/pull/2057) Reduce block times to about three seconds
 * [#1312] Stableswap: Createpool logic 
 * [#1230] Stableswap CFMM equations
 * [#1429] solver for multi-asset CFMM
diff --git a/Makefile b/Makefile
index 85ca45018..90b2d33ea 100644
--- a/Makefile
+++ b/Makefile
@@ -352,7 +352,7 @@ mdlint:
 
 markdown:
 	@docker run -v $(PWD):/workdir ghcr.io/igorshubovych/markdownlint-cli:latest "**/*.md" --fix
-	
+
 ###############################################################################
 ###                                Localnet                                 ###
 ###############################################################################
@@ -361,7 +361,7 @@ localnet-keys:
 	. tests/localosmosis/scripts/add_keys.sh
 
 localnet-build:
-	@docker-compose -f tests/localosmosis/docker-compose.yml build
+	@DOCKER_BUILDKIT=1 docker-compose -f tests/localosmosis/docker-compose.yml build
 
 localnet-build-state-export:
 	@docker build -t local:osmosis-se --build-arg ID=$(ID) -f tests/localosmosis/mainnet_state/Dockerfile-stateExport .
diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go
index cb0a02b08..c0d94c5d1 100644
--- a/app/keepers/keepers.go
+++ b/app/keepers/keepers.go
@@ -308,6 +308,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers(
 		appKeepers.BankKeeper,
 		appKeepers.IncentivesKeeper,
 		appKeepers.DistrKeeper,
+		appKeepers.GAMMKeeper,
 	)
 	appKeepers.PoolIncentivesKeeper = &poolIncentivesKeeper
 
diff --git a/cmd/osmosisd/cmd/root.go b/cmd/osmosisd/cmd/root.go
index c43d54f85..65c472093 100644
--- a/cmd/osmosisd/cmd/root.go
+++ b/cmd/osmosisd/cmd/root.go
@@ -4,7 +4,6 @@ import (
 	"io"
 	"os"
 	"path/filepath"
-	"time"
 
 	"github.com/prometheus/client_golang/prometheus"
 
@@ -75,11 +74,7 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) {
 			if err := client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil {
 				return err
 			}
-
 			customAppTemplate, customAppConfig := initAppConfig()
-			serverCtx := server.GetServerContextFromCmd(cmd)
-			serverCtx.Config.Consensus.TimeoutCommit = 2 * time.Second
-
 			return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig)
 		},
 		SilenceUsage: true,
diff --git a/cmd/querygen/main.go b/cmd/querygen/main.go
index 94ed43d6b..66070ad73 100644
--- a/cmd/querygen/main.go
+++ b/cmd/querygen/main.go
@@ -12,9 +12,7 @@ import (
 	"github.com/osmosis-labs/osmosis/v12/cmd/querygen/templates"
 )
 
-var (
-	grpcTemplate template.Template
-)
+var grpcTemplate template.Template
 
 func main() {
 	err := parseTemplates()
diff --git a/cmd/querygen/templates/queryyml.go b/cmd/querygen/templates/queryyml.go
index 530d5c6e7..410cb88c9 100644
--- a/cmd/querygen/templates/queryyml.go
+++ b/cmd/querygen/templates/queryyml.go
@@ -36,8 +36,7 @@ type ProtoWrapperDescriptor struct {
 	Response      string
 }
 
-type CliDescriptor struct {
-}
+type CliDescriptor struct{}
 
 func ReadYmlFile(filepath string) (QueryYml, error) {
 	content, err := os.ReadFile(filepath) // the file is inside the local directory
diff --git a/go.mod b/go.mod
index 642630604..b6d7bf6a9 100644
--- a/go.mod
+++ b/go.mod
@@ -3,12 +3,12 @@ module github.com/osmosis-labs/osmosis/v12
 go 1.18
 
 require (
-	github.com/CosmWasm/wasmd v0.27.0
+	github.com/CosmWasm/wasmd v0.28.0-osmo-v12
 	github.com/cosmos/cosmos-proto v1.0.0-alpha7
 	github.com/cosmos/cosmos-sdk v0.46.1
 	github.com/cosmos/go-bip39 v1.0.0
-	github.com/cosmos/iavl v0.19.0
-	github.com/cosmos/ibc-go/v3 v3.2.0
+	github.com/cosmos/iavl v0.19.1
+	github.com/cosmos/ibc-go/v3 v3.2.1
 	github.com/gogo/protobuf v1.3.3
 	github.com/golang/mock v1.6.0
 	github.com/golang/protobuf v1.5.2
@@ -167,7 +167,7 @@ require (
 	github.com/hdevalence/ed25519consensus v0.0.0-20210204194344-59a8610d2b87 // indirect
 	github.com/hexops/gotextdiff v1.0.3 // indirect
 	github.com/imdario/mergo v0.3.13 // indirect
-	github.com/improbable-eng/grpc-web v0.14.1 // indirect
+	github.com/improbable-eng/grpc-web v0.15.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/jessevdk/go-flags v1.4.0 // indirect
 	github.com/jgautheron/goconst v1.5.1 // indirect
@@ -291,8 +291,8 @@ require (
 )
 
 replace (
-	// branch: v0.27.0.rc3-osmo, current tag: v0.27.0.rc3-osmo
-	github.com/CosmWasm/wasmd => github.com/osmosis-labs/wasmd v0.27.0-rc2.0.20220517191021-59051aa18d58
+	// branch: v0.28.0x-osmo-v12, current tag: v0.28.0-osmo-v12.1
+	github.com/CosmWasm/wasmd => github.com/osmosis-labs/wasmd v0.28.0-osmo-v12.1
 	// Our cosmos-sdk branch is:  https://github.com/osmosis-labs/cosmos-sdk, current branch: osmosis-main. Direct commit link: https://github.com/osmosis-labs/cosmos-sdk/commit/5c9a51c277d067e0ec5cf48df30a85fae95bcd14
 	github.com/cosmos/cosmos-sdk => github.com/osmosis-labs/cosmos-sdk v0.45.1-0.20220822135534-5c9a51c277d0
 	// Use Osmosis fast iavl
diff --git a/go.sum b/go.sum
index 4bfa12328..bb87f1a0f 100644
--- a/go.sum
+++ b/go.sum
@@ -181,6 +181,7 @@ github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacM
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
 github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -239,8 +240,8 @@ github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY
 github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
 github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y=
 github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw=
-github.com/cosmos/ibc-go/v3 v3.2.0 h1:Mh+RWo5FHPMM1Xsrar3uwKufdEGdIp5LDkVk9cYKYYA=
-github.com/cosmos/ibc-go/v3 v3.2.0/go.mod h1:DrDYXJjWNwgv72cK1Il+BegtyGIDXcx+cnJwWGzve6o=
+github.com/cosmos/ibc-go/v3 v3.2.1 h1:cURaZNryZ7J+UrcirDzrGfuDjvfB28Zd1UJupayFdho=
+github.com/cosmos/ibc-go/v3 v3.2.1/go.mod h1:VUWLHw0C3USmTQZnTdkuXXdUdLbW8zsK3lV1Ieposog=
 github.com/cosmos/interchain-accounts v0.1.0 h1:QmuwNsf1Hxl3P5GSGt7Z+JeuHPiZw4Z34R/038P5T6s=
 github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4=
 github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY=
@@ -633,8 +634,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
 github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
-github.com/improbable-eng/grpc-web v0.14.1 h1:NrN4PY71A6tAz2sKDvC5JCauENWp0ykG8Oq1H3cpFvw=
-github.com/improbable-eng/grpc-web v0.14.1/go.mod h1:zEjGHa8DAlkoOXmswrNvhUGEYQA9UI7DhrGeHR1DMGU=
+github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ=
+github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
@@ -881,8 +882,8 @@ github.com/osmosis-labs/go-mutesting v0.0.0-20220811235203-65a53b4ea8e3 h1:/imbK
 github.com/osmosis-labs/go-mutesting v0.0.0-20220811235203-65a53b4ea8e3/go.mod h1:lV6KnqXYD/ayTe7310MHtM3I2q8Z6bBfMAi+bhwPYtI=
 github.com/osmosis-labs/iavl v0.17.3-osmo-v7 h1:6KcADC/WhL7yDmNQxUIJt2XmzNt4FfRmq9gRke45w74=
 github.com/osmosis-labs/iavl v0.17.3-osmo-v7/go.mod h1:lJEOIlsd3sVO0JDyXWIXa9/Ur5FBscP26zJx0KxHjto=
-github.com/osmosis-labs/wasmd v0.27.0-rc2.0.20220517191021-59051aa18d58 h1:15l3Iss2oCGCeJRi2g3CuCnqmEjpAr3Le7cDnoN/LS0=
-github.com/osmosis-labs/wasmd v0.27.0-rc2.0.20220517191021-59051aa18d58/go.mod h1:0h8WBsFhyingomsrN+34JVZe/qRySHYHICyTEokCkYU=
+github.com/osmosis-labs/wasmd v0.28.0-osmo-v12.1 h1:CZJSa65banZjQNyDhp+nGPr9MRYrIfOU/aO3ww2V1Rg=
+github.com/osmosis-labs/wasmd v0.28.0-osmo-v12.1/go.mod h1:7YWBfoD6zPuu6pmqnq/kMNpc+xqOaxEtolJ5/7xHzB8=
 github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
 github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
 github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
@@ -1356,6 +1357,7 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
@@ -1459,6 +1461,7 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/osmomath/decimal.go b/osmomath/decimal.go
index b6aa442ad..527bab9ca 100644
--- a/osmomath/decimal.go
+++ b/osmomath/decimal.go
@@ -8,6 +8,8 @@ import (
 	"strconv"
 	"strings"
 	"testing"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
 )
 
 // NOTE: never use new(BigDec) or else we will panic unmarshalling into the
@@ -18,11 +20,11 @@ type BigDec struct {
 
 const (
 	// number of decimal places
-	Precision = 18
+	Precision = 36
 
 	// bytes required to represent the above precision
-	// Ceiling[Log2[999 999 999 999 999 999]]
-	DecimalPrecisionBits = 60
+	// Ceiling[Log2[10**Precision - 1]]
+	DecimalPrecisionBits = 120
 
 	maxDecBitLen = maxBitLen + DecimalPrecisionBits
 
@@ -494,6 +496,27 @@ func (d BigDec) MustFloat64() float64 {
 	}
 }
 
+// SdkDec returns the Sdk.Dec representation of a BigDec.
+// Values in any additional decimal places are truncated.
+func (d BigDec) SdkDec() sdk.Dec {
+	precisionDiff := Precision - sdk.Precision
+	precisionFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(precisionDiff)), nil)
+
+	if precisionDiff < 0 {
+		panic("invalid decimal precision")
+	}
+
+	truncatedDec := sdk.NewDecFromBigIntWithPrec(new(big.Int).Quo(d.BigInt(), precisionFactor), sdk.Precision)
+
+	return truncatedDec
+}
+
+// BigDecFromSdkDec returns the BigDec representation of an SdkDec.
+// Values in any additional decimal places are truncated.
+func BigDecFromSdkDec(d sdk.Dec) BigDec {
+	return NewDecFromBigIntWithPrec(d.BigInt(), sdk.Precision)
+}
+
 //     ____
 //  __|    |__   "chop 'em
 //       ` \     round!"
diff --git a/osmomath/int.go b/osmomath/int.go
index 9d4f5e650..c3ae4ef28 100644
--- a/osmomath/int.go
+++ b/osmomath/int.go
@@ -8,7 +8,7 @@ import (
 	"testing"
 )
 
-const maxBitLen = 512
+const maxBitLen = 1024
 
 func newIntegerFromString(s string) (*big.Int, bool) {
 	return new(big.Int).SetString(s, 0)
diff --git a/osmoutils/module_account.go b/osmoutils/module_account.go
new file mode 100644
index 000000000..20c57704c
--- /dev/null
+++ b/osmoutils/module_account.go
@@ -0,0 +1,78 @@
+package osmoutils
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+	vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
+)
+
+type AccountKeeper interface {
+	NewAccount(sdk.Context, authtypes.AccountI) authtypes.AccountI
+
+	GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI
+	SetAccount(ctx sdk.Context, acc authtypes.AccountI)
+}
+
+// CanCreateModuleAccountAtAddr tells us if we can safely make a module account at
+// a given address. By collision resistance of the address (given API safe construction),
+// the only way for an account to be already be at this address is if its claimed by the same
+// pre-image from the correct module,
+// or some SDK command breaks assumptions and creates an account at designated address.
+// This function checks if there is an account at that address, and runs some safety checks
+// to be extra-sure its not a user account (e.g. non-zero sequence, pubkey, of fore-seen account types).
+// If there is no account, or if we believe its not a user-spendable account, we allow module account
+// creation at the address.
+// else, we do not.
+//
+// TODO: This is generally from an SDK design flaw
+// code based off wasmd code: https://github.com/CosmWasm/wasmd/pull/996
+// Its _mandatory_ that the caller do the API safe construction to generate a module account addr,
+// namely, address.Module(ModuleName, {key})
+func CanCreateModuleAccountAtAddr(ctx sdk.Context, ak AccountKeeper, addr sdk.AccAddress) error {
+	existingAcct := ak.GetAccount(ctx, addr)
+	if existingAcct == nil {
+		return nil
+	}
+	if existingAcct.GetSequence() != 0 || existingAcct.GetPubKey() != nil {
+		return fmt.Errorf("cannot create module account %s, "+
+			"due to an account at that address already existing & having sent txs", addr)
+	}
+	var overrideAccountTypes = map[reflect.Type]struct{}{
+		reflect.TypeOf(&authtypes.BaseAccount{}):                 {},
+		reflect.TypeOf(&vestingtypes.DelayedVestingAccount{}):    {},
+		reflect.TypeOf(&vestingtypes.ContinuousVestingAccount{}): {},
+		reflect.TypeOf(&vestingtypes.BaseVestingAccount{}):       {},
+		reflect.TypeOf(&vestingtypes.PeriodicVestingAccount{}):   {},
+		reflect.TypeOf(&vestingtypes.PermanentLockedAccount{}):   {},
+		reflect.TypeOf(&vestingtypes.ClawbackVestingAccount{}):   {},
+	}
+	if _, clear := overrideAccountTypes[reflect.TypeOf(existingAcct)]; clear {
+		return nil
+	}
+	return errors.New("cannot create module account %s, " +
+		"due to an account at that address already existing & not being an overrideable type")
+}
+
+// CreateModuleAccount creates a module account at the provided address.
+// It overrides an account if it exists at that address, with a non-zero sequence number & pubkey
+// Contract: addr is derived from `address.Module(ModuleName, key)`
+func CreateModuleAccount(ctx sdk.Context, ak AccountKeeper, addr sdk.AccAddress) error {
+	err := CanCreateModuleAccountAtAddr(ctx, ak, addr)
+	if err != nil {
+		return err
+	}
+
+	acc := ak.NewAccount(
+		ctx,
+		authtypes.NewModuleAccount(
+			authtypes.NewBaseAccountWithAddress(addr),
+			addr.String(),
+		),
+	)
+	ak.SetAccount(ctx, acc)
+	return nil
+}
diff --git a/osmoutils/store_helper.go b/osmoutils/store_helper.go
index 9aaff7c14..86cabae44 100644
--- a/osmoutils/store_helper.go
+++ b/osmoutils/store_helper.go
@@ -55,7 +55,8 @@ func GetIterValuesWithStop[T any](
 	keyEnd []byte,
 	reverse bool,
 	stopFn func([]byte) bool,
-	parseValue func([]byte) (T, error)) ([]T, error) {
+	parseValue func([]byte) (T, error),
+) ([]T, error) {
 	iter := makeIterator(storeObj, keyStart, keyEnd, reverse)
 	defer iter.Close()
 
diff --git a/proto/osmosis/pool-incentives/v1beta1/genesis.proto b/proto/osmosis/pool-incentives/v1beta1/genesis.proto
index 001ec9702..e04104735 100644
--- a/proto/osmosis/pool-incentives/v1beta1/genesis.proto
+++ b/proto/osmosis/pool-incentives/v1beta1/genesis.proto
@@ -20,4 +20,8 @@ message GenesisState {
     (gogoproto.nullable) = true,
     (gogoproto.moretags) = "yaml:\"distr_info\""
   ];
+  PoolToGauges pool_to_gauges = 4 [
+    (gogoproto.nullable) = true,
+    (gogoproto.moretags) = "yaml:\"pool_to_gauges\""
+  ];
 }
diff --git a/proto/osmosis/pool-incentives/v1beta1/incentives.proto b/proto/osmosis/pool-incentives/v1beta1/incentives.proto
index baa4efc61..7693add19 100644
--- a/proto/osmosis/pool-incentives/v1beta1/incentives.proto
+++ b/proto/osmosis/pool-incentives/v1beta1/incentives.proto
@@ -42,3 +42,17 @@ message DistrRecord {
     (gogoproto.nullable) = false
   ];
 }
+
+message PoolToGauge {
+  uint64 pool_id = 1 [ (gogoproto.moretags) = "yaml:\"pool_id\"" ];
+  uint64 gauge_id = 2 [ (gogoproto.moretags) = "yaml:\"gauge\"" ];
+  google.protobuf.Duration duration = 3 [
+    (gogoproto.nullable) = false,
+    (gogoproto.stdduration) = true,
+    (gogoproto.moretags) = "yaml:\"duration\""
+  ];
+}
+
+message PoolToGauges {
+  repeated PoolToGauge pool_to_gauge = 2 [ (gogoproto.nullable) = false ];
+}
\ No newline at end of file
diff --git a/proto/osmosis/streamswap/v1/tx.proto b/proto/osmosis/streamswap/v1/tx.proto
index 32029e3c5..087a35e03 100644
--- a/proto/osmosis/streamswap/v1/tx.proto
+++ b/proto/osmosis/streamswap/v1/tx.proto
@@ -32,13 +32,13 @@ service Msg {
   rpc Withdraw(MsgWithdraw) returns (google.protobuf.Empty);
 
   // ExitSale withdraws (by a user who subscribed to the sale) purchased
-  // tokens_out from the pool and remained tokens_in. Must be called before
+  // tokens_out from the pool and remained tokens_in. Must be called after
   // the sale end.
   rpc ExitSale(MsgExitSale) returns (MsgExitSaleResponse);
 
   // FinalizeSale clean ups the sale and sends income (earned tokens_in) to the
-  // Sale recipient. Returns error if called before the Sale end. Anyone can
-  // call this method.
+  // Sale recipient. Returns error if called before the Sale end or it was
+  // already finalized. Anyone can call this method.
   rpc FinalizeSale(MsgFinalizeSale) returns (MsgFinalizeSaleResponse);
 }
 
diff --git a/simulation/executor/mock_tendermint.go b/simulation/executor/mock_tendermint.go
index 9270970d0..6e21f7da8 100644
--- a/simulation/executor/mock_tendermint.go
+++ b/simulation/executor/mock_tendermint.go
@@ -217,7 +217,8 @@ func randomVoteInfos(r *rand.Rand, simParams Params, validators mockValidators,
 func randomDoubleSignEvidence(r *rand.Rand, params Params,
 	validators mockValidators, pastTimes []time.Time,
 	pastVoteInfos [][]abci.VoteInfo,
-	event func(route, op, evResult string), header tmproto.Header, voteInfos []abci.VoteInfo) []abci.Evidence {
+	event func(route, op, evResult string), header tmproto.Header, voteInfos []abci.VoteInfo,
+) []abci.Evidence {
 	evidence := []abci.Evidence{}
 	// return if no past times or if only 10 validators remaining in the active set
 	if len(pastTimes) == 0 {
diff --git a/simulation/executor/simulate.go b/simulation/executor/simulate.go
index 0130255ec..268d6600b 100644
--- a/simulation/executor/simulate.go
+++ b/simulation/executor/simulate.go
@@ -116,7 +116,8 @@ func cursedInitializationLogic(
 	w io.Writer,
 	app simtypes.App,
 	initFunctions InitFunctions,
-	config *Config) (*simtypes.SimCtx, *simState, Params, error) {
+	config *Config,
+) (*simtypes.SimCtx, *simState, Params, error) {
 	fmt.Fprintf(w, "Starting SimulateFromSeed with randomness created with seed %d\n", int(config.Seed))
 
 	r := rand.New(rand.NewSource(config.Seed))
@@ -267,7 +268,8 @@ func createBlockSimulator(testingMode bool, w io.Writer, params Params, actions
 // This is inheriting old functionality. We should break this as part of making logging be usable / make sense.
 func (simState *simState) logActionResult(
 	header tmproto.Header, actionIndex int,
-	opMsg simulation.OperationMsg, resultData []byte, stats statsDb, actionErr error) error {
+	opMsg simulation.OperationMsg, resultData []byte, stats statsDb, actionErr error,
+) error {
 	opMsg.LogEvent(simState.eventStats.Tally)
 	err := stats.logActionResult(header, opMsg, resultData)
 	if err != nil {
diff --git a/simulation/executor/simulate_dev.go b/simulation/executor/simulate_dev.go
index a606e36a0..85dd4b6ec 100644
--- a/simulation/executor/simulate_dev.go
+++ b/simulation/executor/simulate_dev.go
@@ -69,7 +69,8 @@ func newSimulatorState(simParams Params, initialHeader tmproto.Header, tb testin
 func (simState *simState) SimulateAllBlocks(
 	w io.Writer,
 	simCtx *simtypes.SimCtx,
-	blockSimulator blockSimFn) (stopEarly bool, err error) {
+	blockSimulator blockSimFn,
+) (stopEarly bool, err error) {
 	stopEarly = false
 	initialHeight := simState.config.InitializationConfig.InitialBlockHeight
 	numBlocks := simState.config.NumBlocks
@@ -101,7 +102,7 @@ func (simState *simState) SimulateBlock(simCtx *simtypes.SimCtx, blockSimulator
 	}
 
 	requestBeginBlock := simState.beginBlock(simCtx)
-	ctx := simCtx.BaseApp().NewContext(false, simState.header)
+	ctx := simCtx.BaseApp().NewContext(false, simState.header).WithBlockTime(simState.header.Time)
 
 	// Run queued operations. Ignores blocksize if blocksize is too small
 	numQueuedOpsRan, err := simState.runQueuedOperations(simCtx, ctx)
diff --git a/simulation/simtypes/action.go b/simulation/simtypes/action.go
index 01a33d4cb..bd29ad067 100644
--- a/simulation/simtypes/action.go
+++ b/simulation/simtypes/action.go
@@ -41,6 +41,7 @@ func (a weightedOperationAction) Name() string                     { return fmt.
 func (a weightedOperationAction) Frequency() Frequency {
 	return Frequency(mapFrequencyFromInt(a.op.Weight()))
 }
+
 func (a weightedOperationAction) Execute(sim *SimCtx, ctx sdk.Context) (
 	simulation.OperationMsg, []simulation.FutureOperation, []byte, error,
 ) {
diff --git a/simulation/simtypes/simlogger/logger.go b/simulation/simtypes/simlogger/logger.go
index 7537556ad..0d0a00815 100644
--- a/simulation/simtypes/simlogger/logger.go
+++ b/simulation/simtypes/simlogger/logger.go
@@ -19,9 +19,11 @@ func (s *simLogger) Debug(msg string, keyvals ...interface{}) {
 	}
 	s.logger.Debug(msg, keyvals...)
 }
+
 func (s *simLogger) Info(msg string, keyvals ...interface{}) {
 	s.logger.Info(msg, keyvals)
 }
+
 func (s *simLogger) Error(msg string, keyvals ...interface{}) {
 	s.logger.Error(msg, keyvals)
 }
diff --git a/wasmbinding/stargate_whitelist.go b/wasmbinding/stargate_whitelist.go
index 508489e56..8c02acbfe 100644
--- a/wasmbinding/stargate_whitelist.go
+++ b/wasmbinding/stargate_whitelist.go
@@ -66,14 +66,14 @@ func init() {
 
 	// osmosis queries
 
-	//epochs
+	// epochs
 	setWhitelistedQuery("/osmosis.epochs.v1beta1.Query/EpochInfos", &epochtypes.QueryEpochsInfoResponse{})
 	setWhitelistedQuery("/osmosis.epochs.v1beta1.Query/CurrentEpoch", &epochtypes.QueryCurrentEpochResponse{})
 
 	// gamm
 	setWhitelistedQuery("/osmosis.gamm.v1beta1.Query/NumPools", &gammtypes.QueryNumPoolsResponse{})
 	setWhitelistedQuery("/osmosis.gamm.v1beta1.Query/TotalLiquidity", &gammtypes.QueryTotalLiquidityResponse{})
-	setWhitelistedQuery("/osmosis.gamm.v1beta1.Query/Pool", &gammtypes.QueryPoolResponse{})
+	// setWhitelistedQuery("/osmosis.gamm.v1beta1.Query/Pool", &gammtypes.QueryPoolResponse{}) - we block this
 	setWhitelistedQuery("/osmosis.gamm.v1beta1.Query/PoolParams", &gammtypes.QueryPoolParamsResponse{})
 	setWhitelistedQuery("/osmosis.gamm.v1beta1.Query/TotalPoolLiquidity", &gammtypes.QueryTotalPoolLiquidityResponse{})
 	setWhitelistedQuery("/osmosis.gamm.v1beta1.Query/TotalShares", &gammtypes.QueryTotalSharesResponse{})
diff --git a/x/epochs/module.go b/x/epochs/module.go
index 72183e98c..10508c607 100644
--- a/x/epochs/module.go
+++ b/x/epochs/module.go
@@ -43,8 +43,7 @@ var (
 // ----------------------------------------------------------------------------
 
 // AppModuleBasic implements the AppModuleBasic interface for the capability module.
-type AppModuleBasic struct {
-}
+type AppModuleBasic struct{}
 
 func NewAppModuleBasic() AppModuleBasic {
 	return AppModuleBasic{}
diff --git a/x/gamm/keeper/pool_service.go b/x/gamm/keeper/pool_service.go
index 12609257f..0f630e10a 100644
--- a/x/gamm/keeper/pool_service.go
+++ b/x/gamm/keeper/pool_service.go
@@ -5,10 +5,10 @@ import (
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
-	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
 	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
 
 	"github.com/osmosis-labs/osmosis/v12/osmomath"
+	"github.com/osmosis-labs/osmosis/v12/osmoutils"
 	"github.com/osmosis-labs/osmosis/v12/x/gamm/types"
 )
 
@@ -101,10 +101,6 @@ func (k Keeper) validateCreatedPool(
 		return sdkerrors.Wrapf(types.ErrInvalidPool,
 			"Pool was attempted to be created with incorrect number of initial shares.")
 	}
-	acc := k.accountKeeper.GetAccount(ctx, pool.GetAddress())
-	if acc != nil {
-		return sdkerrors.Wrapf(types.ErrPoolAlreadyExist, "pool %d already exist", poolId)
-	}
 	return nil
 }
 
@@ -143,14 +139,9 @@ func (k Keeper) CreatePool(ctx sdk.Context, msg types.CreatePoolMsg) (uint64, er
 	}
 
 	// create and save the pool's module account to the account keeper
-	acc := k.accountKeeper.NewAccount(
-		ctx,
-		authtypes.NewModuleAccount(
-			authtypes.NewBaseAccountWithAddress(pool.GetAddress()),
-			pool.GetAddress().String(),
-		),
-	)
-	k.accountKeeper.SetAccount(ctx, acc)
+	if err := osmoutils.CreateModuleAccount(ctx, k.accountKeeper, pool.GetAddress()); err != nil {
+		return 0, fmt.Errorf("creating pool module account for id %d: %w", poolId, err)
+	}
 
 	// send initial liquidity to the pool
 	err = k.bankKeeper.SendCoins(ctx, sender, pool.GetAddress(), initialPoolLiquidity)
diff --git a/x/gamm/pool-models/stableswap/amm.go b/x/gamm/pool-models/stableswap/amm.go
index 21ef11b00..186c39b4a 100644
--- a/x/gamm/pool-models/stableswap/amm.go
+++ b/x/gamm/pool-models/stableswap/amm.go
@@ -5,17 +5,21 @@ import (
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
+	"github.com/osmosis-labs/osmosis/v12/osmomath"
 	"github.com/osmosis-labs/osmosis/v12/x/gamm/pool-models/internal/cfmm_common"
 	types "github.com/osmosis-labs/osmosis/v12/x/gamm/types"
 )
 
 var (
-	cubeRootTwo, _   = sdk.NewDec(2).ApproxRoot(3)
+	cubeRootTwo, _   = osmomath.NewBigDec(2).ApproxRoot(3)
 	threeCubeRootTwo = cubeRootTwo.MulInt64(3)
 )
 
 // solidly CFMM is xy(x^2 + y^2) = k
-func cfmmConstant(xReserve, yReserve sdk.Dec) sdk.Dec {
+func cfmmConstant(xReserve, yReserve osmomath.BigDec) osmomath.BigDec {
+	if !xReserve.IsPositive() || !yReserve.IsPositive() {
+		panic("invalid input: reserves must be positive")
+	}
 	xy := xReserve.Mul(yReserve)
 	x2 := xReserve.Mul(xReserve)
 	y2 := yReserve.Mul(yReserve)
@@ -27,7 +31,11 @@ func cfmmConstant(xReserve, yReserve sdk.Dec) sdk.Dec {
 // outside of x and y (e.g. u = wz), and v is the sum
 // of their squares (e.g. v = w^2 + z^2).
 // When u = 1 and v = 0, this is equivalent to solidly's CFMM
-func cfmmConstantMulti(xReserve, yReserve, uReserve, vSumSquares sdk.Dec) sdk.Dec {
+func cfmmConstantMulti(xReserve, yReserve, uReserve, vSumSquares osmomath.BigDec) osmomath.BigDec {
+	if !xReserve.IsPositive() || !yReserve.IsPositive() || !uReserve.IsPositive() || vSumSquares.IsNegative() {
+		panic("invalid input: reserves must be positive")
+	}
+
 	xyu := xReserve.Mul(yReserve.Mul(uReserve))
 	x2 := xReserve.Mul(xReserve)
 	y2 := yReserve.Mul(yReserve)
@@ -39,9 +47,9 @@ func cfmmConstantMulti(xReserve, yReserve, uReserve, vSumSquares sdk.Dec) sdk.De
 // how many units `a` of x do we get out.
 // So we solve the following expression for `a`
 // xy(x^2 + y^2) = (x - a)(y + b)((x - a)^2 + (y + b)^2)
-func solveCfmm(xReserve, yReserve, yIn sdk.Dec) sdk.Dec {
-	if !yReserve.Add(yIn).IsPositive() {
-		panic("invalid yReserve, yIn combo")
+func solveCfmm(xReserve, yReserve, yIn osmomath.BigDec) osmomath.BigDec {
+	if !xReserve.IsPositive() || !yReserve.IsPositive() || !yIn.IsPositive() {
+		panic("invalid input: reserves and input must be positive")
 	}
 
 	// use the following wolfram alpha link to solve the equation
@@ -87,7 +95,7 @@ func solveCfmm(xReserve, yReserve, yIn sdk.Dec) sdk.Dec {
 	// and maybe in state.
 	x := xReserve
 	y := yReserve
-	x2py2 := x.Mul(x).AddMut(y.Mul(y))
+	x2py2 := x.Mul(x).Add(y.Mul(y))
 
 	xy := x.Mul(y)
 
@@ -121,19 +129,19 @@ func solveCfmm(xReserve, yReserve, yIn sdk.Dec) sdk.Dec {
 	e := xy.Mul(x2py2) // xy(x^2 + y^2)
 
 	// t1 = -27 (b + y)^2 e
-	t1 := e.Mul(bpy2).MulInt64Mut(-27)
+	t1 := e.Mul(bpy2).MulInt64(-27)
 
 	// compute d = (b + y)^2 sqrt(729 e^2 + 108 (b+y)^8)
 	bpy8 := bpy4.Mul(bpy4)
-	sqrt_inner := e.MulMut(e).MulInt64Mut(729).AddMut(bpy8.MulInt64Mut(108)) // 729 e^2 + 108 (b+y)^8
+	sqrt_inner := e.Mul(e).MulInt64(729).Add(bpy8.MulInt64(108)) // 729 e^2 + 108 (b+y)^8
 	sqrt, err := sqrt_inner.ApproxSqrt()
 	if err != nil {
 		panic(err)
 	}
-	d := sqrt.MulMut(bpy2)
+	d := sqrt.Mul(bpy2)
 
 	// foo = (t1 + d)^(1/3)
-	foo3 := t1.AddMut(d)
+	foo3 := t1.Add(d)
 	foo, _ := foo3.ApproxRoot(3)
 
 	// a = {foo / (3 2^(1/3) (b + y))}
@@ -151,6 +159,11 @@ func solveCfmm(xReserve, yReserve, yIn sdk.Dec) sdk.Dec {
 	term3 := term3Numerator.Quo(bpy)
 
 	a := term1.Sub(term2).Add(term3)
+
+	if a.GTE(xReserve) {
+		panic("invalid output: greater than full pool reserves")
+	}
+
 	return a
 }
 
@@ -159,9 +172,9 @@ func solveCfmm(xReserve, yReserve, yIn sdk.Dec) sdk.Dec {
 // how many units `a` of x do we get out.
 // So we solve the following expression for `a`
 // xyz(x^2 + y^2 + w) = (x - a)(y + b)z((x - a)^2 + (y + b)^2 + w)
-func solveCfmmMulti(xReserve, yReserve, wSumSquares, yIn sdk.Dec) sdk.Dec {
-	if !yReserve.Add(yIn).IsPositive() {
-		panic("invalid yReserve, yIn combo")
+func solveCfmmMulti(xReserve, yReserve, wSumSquares, yIn osmomath.BigDec) osmomath.BigDec {
+	if !xReserve.IsPositive() || !yReserve.IsPositive() || !yIn.IsPositive() {
+		panic("invalid input: reserves and input must be positive")
 	}
 
 	// Use the following wolfram alpha link to solve the equation
@@ -256,26 +269,30 @@ func solveCfmmMulti(xReserve, yReserve, wSumSquares, yIn sdk.Dec) sdk.Dec {
 
 	a := term1.Sub(term2).Add(term3)
 
+	if a.GTE(xReserve) {
+		panic("invalid output: greater than full pool reserves")
+	}
+
 	return a
 }
 
-func approxDecEqual(a, b, tol sdk.Dec) bool {
+func approxDecEqual(a, b, tol osmomath.BigDec) bool {
 	diff := a.Sub(b).Abs()
 	return diff.Quo(a).LTE(tol) && diff.Quo(b).LTE(tol)
 }
 
 var (
-	twodec    = sdk.MustNewDecFromStr("2.0")
-	threshold = sdk.NewDecWithPrec(1, 10) // Correct within a factor of 1 * 10^{-10}
+	twodec    = osmomath.MustNewDecFromStr("2.0")
+	threshold = osmomath.NewDecWithPrec(1, 10) // Correct within a factor of 1 * 10^{-10}
 )
 
 // solveCFMMBinarySearch searches the correct dx using binary search over constant K.
 // added for future extension
-func solveCFMMBinarySearch(constantFunction func(sdk.Dec, sdk.Dec) sdk.Dec) func(sdk.Dec, sdk.Dec, sdk.Dec) sdk.Dec {
-	return func(xReserve, yReserve, yIn sdk.Dec) sdk.Dec {
+func solveCFMMBinarySearch(constantFunction func(osmomath.BigDec, osmomath.BigDec) osmomath.BigDec) func(osmomath.BigDec, osmomath.BigDec, osmomath.BigDec) osmomath.BigDec {
+	return func(xReserve, yReserve, yIn osmomath.BigDec) osmomath.BigDec {
 		k := constantFunction(xReserve, yReserve)
 		yf := yReserve.Add(yIn)
-		x_low_est := sdk.ZeroDec()
+		x_low_est := osmomath.ZeroDec()
 		x_high_est := xReserve
 		x_est := (x_high_est.Add(x_low_est)).Quo(twodec)
 		cur_k := constantFunction(x_est, yf)
@@ -294,11 +311,11 @@ func solveCFMMBinarySearch(constantFunction func(sdk.Dec, sdk.Dec) sdk.Dec) func
 
 // solveCFMMBinarySearch searches the correct dx using binary search over constant K.
 // added for future extension
-func solveCFMMBinarySearchMulti(constantFunction func(sdk.Dec, sdk.Dec, sdk.Dec, sdk.Dec) sdk.Dec) func(sdk.Dec, sdk.Dec, sdk.Dec, sdk.Dec, sdk.Dec) sdk.Dec {
-	return func(xReserve, yReserve, uReserve, wSumSquares, yIn sdk.Dec) sdk.Dec {
+func solveCFMMBinarySearchMulti(constantFunction func(osmomath.BigDec, osmomath.BigDec, osmomath.BigDec, osmomath.BigDec) osmomath.BigDec) func(osmomath.BigDec, osmomath.BigDec, osmomath.BigDec, osmomath.BigDec, osmomath.BigDec) osmomath.BigDec {
+	return func(xReserve, yReserve, uReserve, wSumSquares, yIn osmomath.BigDec) osmomath.BigDec {
 		k := constantFunction(xReserve, yReserve, uReserve, wSumSquares)
 		yf := yReserve.Add(yIn)
-		x_low_est := sdk.ZeroDec()
+		x_low_est := osmomath.ZeroDec()
 		x_high_est := xReserve
 		x_est := (x_high_est.Add(x_low_est)).Quo(twodec)
 		cur_k := constantFunction(x_est, yf, uReserve, wSumSquares)
@@ -315,7 +332,7 @@ func solveCFMMBinarySearchMulti(constantFunction func(sdk.Dec, sdk.Dec, sdk.Dec,
 	}
 }
 
-func spotPrice(baseReserve, quoteReserve sdk.Dec) sdk.Dec {
+func spotPrice(baseReserve, quoteReserve osmomath.BigDec) osmomath.BigDec {
 	// y = baseAsset, x = quoteAsset
 	// Define f_{y -> x}(a) as the function that outputs the amount of tokens X you'd get by
 	// trading "a" units of Y against the pool, assuming 0 swap fee, at the current liquidity.
@@ -330,7 +347,7 @@ func spotPrice(baseReserve, quoteReserve sdk.Dec) sdk.Dec {
 
 	// We arbitrarily choose a = 1, and anticipate that this is a small value at the scale of
 	// xReserve & yReserve.
-	a := sdk.OneDec()
+	a := osmomath.OneDec()
 	// no need to divide by a, since a = 1.
 	return solveCfmm(baseReserve, quoteReserve, a)
 }
@@ -343,9 +360,9 @@ func (p *Pool) calcOutAmtGivenIn(tokenIn sdk.Coin, tokenOutDenom string, swapFee
 	}
 	tokenInSupply, tokenOutSupply := reserves[0], reserves[1]
 	// We are solving for the amount of token out, hence x = tokenOutSupply, y = tokenInSupply
-	cfmmOut := solveCfmm(tokenOutSupply, tokenInSupply, tokenIn.Amount.ToDec())
+	cfmmOut := solveCfmm(osmomath.BigDecFromSdkDec(tokenOutSupply), osmomath.BigDecFromSdkDec(tokenInSupply), osmomath.BigDecFromSdkDec(tokenIn.Amount.ToDec()))
 	outAmt := p.getDescaledPoolAmt(tokenOutDenom, cfmmOut)
-	return outAmt, nil
+	return outAmt.SdkDec(), nil
 }
 
 // returns inAmt as a decimal
@@ -357,9 +374,9 @@ func (p *Pool) calcInAmtGivenOut(tokenOut sdk.Coin, tokenInDenom string, swapFee
 	tokenInSupply, tokenOutSupply := reserves[0], reserves[1]
 	// We are solving for the amount of token in, cfmm(x,y) = cfmm(x + x_in, y - y_out)
 	// x = tokenInSupply, y = tokenOutSupply, yIn = -tokenOutAmount
-	cfmmIn := solveCfmm(tokenInSupply, tokenOutSupply, tokenOut.Amount.ToDec().Neg())
-	inAmt := p.getDescaledPoolAmt(tokenInDenom, cfmmIn.NegMut())
-	return inAmt, nil
+	cfmmIn := solveCfmm(osmomath.BigDecFromSdkDec(tokenInSupply), osmomath.BigDecFromSdkDec(tokenOutSupply), osmomath.BigDecFromSdkDec(tokenOut.Amount.ToDec().Neg()))
+	inAmt := p.getDescaledPoolAmt(tokenInDenom, cfmmIn.Neg())
+	return inAmt.SdkDec(), nil
 }
 
 func (p *Pool) calcSingleAssetJoinShares(tokenIn sdk.Coin, swapFee sdk.Dec) (sdk.Int, error) {
diff --git a/x/gamm/pool-models/stableswap/pool.go b/x/gamm/pool-models/stableswap/pool.go
index 464c7a569..d88e63ab2 100644
--- a/x/gamm/pool-models/stableswap/pool.go
+++ b/x/gamm/pool-models/stableswap/pool.go
@@ -9,6 +9,7 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
 
+	"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"
 )
@@ -128,7 +129,7 @@ func (p Pool) getScaledPoolAmts(denoms ...string) ([]sdk.Dec, error) {
 }
 
 // getDescaledPoolAmts gets descaled amount of given denom and amount
-func (p Pool) getDescaledPoolAmt(denom string, amount sdk.Dec) sdk.Dec {
+func (p Pool) getDescaledPoolAmt(denom string, amount osmomath.BigDec) osmomath.BigDec {
 	liquidityIndexes := p.getLiquidityIndexMap()
 	liquidityIndex := liquidityIndexes[denom]
 
@@ -241,10 +242,12 @@ func (p Pool) SpotPrice(ctx sdk.Context, baseAssetDenom string, quoteAssetDenom
 	if err != nil {
 		return sdk.Dec{}, err
 	}
-	scaledSpotPrice := spotPrice(reserves[0], reserves[1])
+
+	scaledSpotPrice := spotPrice(osmomath.BigDecFromSdkDec(reserves[0]), osmomath.BigDecFromSdkDec(reserves[1]))
 	spotPrice := p.getDescaledPoolAmt(baseAssetDenom, scaledSpotPrice)
+	spotPriceSdkDec := spotPrice.SdkDec()
 
-	return spotPrice, nil
+	return spotPriceSdkDec, nil
 }
 
 func (p Pool) Copy() Pool {
diff --git a/x/gamm/simulation/sim_msgs.go b/x/gamm/simulation/sim_msgs.go
index 5728380c4..7ceebefe1 100644
--- a/x/gamm/simulation/sim_msgs.go
+++ b/x/gamm/simulation/sim_msgs.go
@@ -355,7 +355,7 @@ func RandomExitSwapShareAmountIn(k keeper.Keeper, sim *simtypes.SimCtx, ctx sdk.
 		PoolId:            pool_id,
 		TokenOutDenom:     tokenOut.Denom,
 		ShareInAmount:     gammShares.Amount.Quo(sdk.NewInt(2)),
-		TokenOutMinAmount: tokenOut.Amount,
+		TokenOutMinAmount: tokenOut.Amount.Quo(sdk.NewInt(2)),
 	}, nil
 }
 
diff --git a/x/incentives/keeper/genesis.go b/x/incentives/keeper/genesis.go
index 6325d2a09..5b1bb8283 100644
--- a/x/incentives/keeper/genesis.go
+++ b/x/incentives/keeper/genesis.go
@@ -25,5 +25,6 @@ func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState {
 		Params:            k.GetParams(ctx),
 		LockableDurations: k.GetLockableDurations(ctx),
 		Gauges:            k.GetNotFinishedGauges(ctx),
+		LastGaugeId:       k.GetLastGaugeID(ctx),
 	}
 }
diff --git a/x/incentives/module.go b/x/incentives/module.go
index d583f2d24..2cdb56ce3 100644
--- a/x/incentives/module.go
+++ b/x/incentives/module.go
@@ -44,8 +44,7 @@ var (
 // ----------------------------------------------------------------------------
 
 // Implements the AppModuleBasic interface for the module.
-type AppModuleBasic struct {
-}
+type AppModuleBasic struct{}
 
 // NewAppModuleBasic creates a new AppModuleBasic struct.
 func NewAppModuleBasic() AppModuleBasic {
diff --git a/x/lockup/module.go b/x/lockup/module.go
index 10cf24efb..a8702257c 100644
--- a/x/lockup/module.go
+++ b/x/lockup/module.go
@@ -47,8 +47,7 @@ var (
 // ----------------------------------------------------------------------------
 
 // AppModuleBasic implements the AppModuleBasic interface for the capability module.
-type AppModuleBasic struct {
-}
+type AppModuleBasic struct{}
 
 func NewAppModuleBasic() AppModuleBasic {
 	return AppModuleBasic{}
diff --git a/x/lockup/types/msgs.go b/x/lockup/types/msgs.go
index 1fb8e83d5..b456cc01c 100644
--- a/x/lockup/types/msgs.go
+++ b/x/lockup/types/msgs.go
@@ -111,12 +111,12 @@ func (m MsgBeginUnlocking) ValidateBasic() error {
 		return fmt.Errorf("invalid lockup ID, got %v", m.ID)
 	}
 
-	// only allow unlocks with a single denom
-	if m.Coins.Len() != 1 {
+	// only allow unlocks with a single denom or empty
+	if m.Coins.Len() > 1 {
 		return fmt.Errorf("can only unlock one denom per lock ID, got %v", m.Coins)
 	}
 
-	if !m.Coins.IsAllPositive() {
+	if !m.Coins.Empty() && !m.Coins.IsAllPositive() {
 		return fmt.Errorf("cannot unlock a zero or negative amount")
 	}
 
diff --git a/x/pool-incentives/client/proposal_handler.go b/x/pool-incentives/client/proposal_handler.go
index 27356cb93..676239dfb 100644
--- a/x/pool-incentives/client/proposal_handler.go
+++ b/x/pool-incentives/client/proposal_handler.go
@@ -7,5 +7,7 @@ import (
 	govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
 )
 
-var UpdatePoolIncentivesHandler = govclient.NewProposalHandler(cli.NewCmdSubmitUpdatePoolIncentivesProposal, rest.ProposalUpdatePoolIncentivesRESTHandler)
-var ReplacePoolIncentivesHandler = govclient.NewProposalHandler(cli.NewCmdSubmitReplacePoolIncentivesProposal, rest.ProposalReplacePoolIncentivesRESTHandler)
+var (
+	UpdatePoolIncentivesHandler  = govclient.NewProposalHandler(cli.NewCmdSubmitUpdatePoolIncentivesProposal, rest.ProposalUpdatePoolIncentivesRESTHandler)
+	ReplacePoolIncentivesHandler = govclient.NewProposalHandler(cli.NewCmdSubmitReplacePoolIncentivesProposal, rest.ProposalReplacePoolIncentivesRESTHandler)
+)
diff --git a/x/pool-incentives/keeper/genesis.go b/x/pool-incentives/keeper/genesis.go
index afecf1ec8..28a060f16 100644
--- a/x/pool-incentives/keeper/genesis.go
+++ b/x/pool-incentives/keeper/genesis.go
@@ -17,14 +17,36 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) {
 	} else {
 		k.SetDistrInfo(ctx, *genState.DistrInfo)
 	}
+	if genState.PoolToGauges != nil {
+		for _, record := range genState.PoolToGauges.PoolToGauge {
+			k.SetPoolGaugeId(ctx, record.PoolId, record.Duration, record.GaugeId)
+		}
+	}
 }
 
 func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState {
 	distrInfo := k.GetDistrInfo(ctx)
+	lastPoolId := k.gammKeeper.GetNextPoolId(ctx)
+	lockableDurations := k.GetLockableDurations(ctx)
+	var poolToGauges types.PoolToGauges
+	for i := 1; i < int(lastPoolId); i++ {
+		for _, duration := range lockableDurations {
+			gaugeID, err := k.GetPoolGaugeId(ctx, uint64(i), duration)
+			if err != nil {
+				panic(err)
+			}
+			var poolToGauge types.PoolToGauge
+			poolToGauge.Duration = duration
+			poolToGauge.GaugeId = gaugeID
+			poolToGauge.PoolId = uint64(i)
+			poolToGauges.PoolToGauge = append(poolToGauges.PoolToGauge, poolToGauge)
+		}
+	}
 
 	return &types.GenesisState{
 		Params:            k.GetParams(ctx),
 		LockableDurations: k.GetLockableDurations(ctx),
 		DistrInfo:         &distrInfo,
+		PoolToGauges:      &poolToGauges,
 	}
 }
diff --git a/x/pool-incentives/keeper/keeper.go b/x/pool-incentives/keeper/keeper.go
index 3da2545f4..8cce08834 100644
--- a/x/pool-incentives/keeper/keeper.go
+++ b/x/pool-incentives/keeper/keeper.go
@@ -27,9 +27,10 @@ type Keeper struct {
 	bankKeeper       types.BankKeeper
 	incentivesKeeper types.IncentivesKeeper
 	distrKeeper      types.DistrKeeper
+	gammKeeper       types.GAMMKeeper
 }
 
-func NewKeeper(cdc codec.BinaryCodec, storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, incentivesKeeper types.IncentivesKeeper, distrKeeper types.DistrKeeper) Keeper {
+func NewKeeper(cdc codec.BinaryCodec, storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, incentivesKeeper types.IncentivesKeeper, distrKeeper types.DistrKeeper, gammKeeper types.GAMMKeeper) Keeper {
 	// ensure pool-incentives module account is set
 	if addr := accountKeeper.GetModuleAddress(types.ModuleName); addr == nil {
 		panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
@@ -50,6 +51,7 @@ func NewKeeper(cdc codec.BinaryCodec, storeKey sdk.StoreKey, paramSpace paramtyp
 		bankKeeper:       bankKeeper,
 		incentivesKeeper: incentivesKeeper,
 		distrKeeper:      distrKeeper,
+		gammKeeper:       gammKeeper,
 	}
 }
 
diff --git a/x/pool-incentives/module.go b/x/pool-incentives/module.go
index cc7b07b3c..8c5b4e301 100644
--- a/x/pool-incentives/module.go
+++ b/x/pool-incentives/module.go
@@ -40,8 +40,7 @@ var (
 	_ module.AppModuleSimulation = AppModule{}
 )
 
-type AppModuleBasic struct {
-}
+type AppModuleBasic struct{}
 
 // Name returns the pool-incentives module's name.
 func (AppModuleBasic) Name() string { return types.ModuleName }
diff --git a/x/pool-incentives/types/expected_keepers.go b/x/pool-incentives/types/expected_keepers.go
index e412ce49f..569bced73 100644
--- a/x/pool-incentives/types/expected_keepers.go
+++ b/x/pool-incentives/types/expected_keepers.go
@@ -7,7 +7,6 @@ import (
 	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
 	distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
 
-	gammtypes "github.com/osmosis-labs/osmosis/v12/x/gamm/types"
 	incentivestypes "github.com/osmosis-labs/osmosis/v12/x/incentives/types"
 	lockuptypes "github.com/osmosis-labs/osmosis/v12/x/lockup/types"
 )
@@ -25,7 +24,7 @@ type BankKeeper interface {
 
 // GAMMKeeper gets the pool interface from poolID.
 type GAMMKeeper interface {
-	GetPool(ctx sdk.Context, poolId uint64) (gammtypes.PoolI, error)
+	GetNextPoolId(ctx sdk.Context) uint64
 }
 
 // IncentivesKeeper creates and gets gauges, and also allows additions to gauge rewards.
diff --git a/x/pool-incentives/types/genesis.pb.go b/x/pool-incentives/types/genesis.pb.go
index 6bde0626e..45ff5dfaa 100644
--- a/x/pool-incentives/types/genesis.pb.go
+++ b/x/pool-incentives/types/genesis.pb.go
@@ -33,6 +33,7 @@ type GenesisState struct {
 	Params            Params          `protobuf:"bytes,1,opt,name=params,proto3" json:"params"`
 	LockableDurations []time.Duration `protobuf:"bytes,2,rep,name=lockable_durations,json=lockableDurations,proto3,stdduration" json:"lockable_durations" yaml:"lockable_durations"`
 	DistrInfo         *DistrInfo      `protobuf:"bytes,3,opt,name=distr_info,json=distrInfo,proto3" json:"distr_info,omitempty" yaml:"distr_info"`
+	PoolToGauges      *PoolToGauges   `protobuf:"bytes,4,opt,name=pool_to_gauges,json=poolToGauges,proto3" json:"pool_to_gauges,omitempty" yaml:"pool_to_gauges"`
 }
 
 func (m *GenesisState) Reset()         { *m = GenesisState{} }
@@ -89,6 +90,13 @@ func (m *GenesisState) GetDistrInfo() *DistrInfo {
 	return nil
 }
 
+func (m *GenesisState) GetPoolToGauges() *PoolToGauges {
+	if m != nil {
+		return m.PoolToGauges
+	}
+	return nil
+}
+
 func init() {
 	proto.RegisterType((*GenesisState)(nil), "osmosis.poolincentives.v1beta1.GenesisState")
 }
@@ -98,29 +106,32 @@ func init() {
 }
 
 var fileDescriptor_cc1f078212600632 = []byte{
-	// 350 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x91, 0xcf, 0x4e, 0xc2, 0x40,
-	0x10, 0x87, 0xbb, 0x60, 0x48, 0x2c, 0x5e, 0x68, 0x3c, 0x00, 0x87, 0x2d, 0x69, 0xa2, 0xc1, 0x03,
-	0xbb, 0x82, 0x37, 0xbd, 0x11, 0x12, 0xe3, 0xcd, 0x60, 0xbc, 0x78, 0x21, 0xdb, 0xb2, 0xd4, 0x8d,
-	0x6d, 0xa7, 0xe9, 0x2e, 0x44, 0xde, 0xc2, 0xa3, 0xcf, 0xe0, 0x93, 0x70, 0xe4, 0xe8, 0xa9, 0x1a,
-	0xfa, 0x06, 0x3c, 0x81, 0xe9, 0x3f, 0x31, 0x21, 0x91, 0x5b, 0x27, 0xf3, 0xfd, 0x66, 0xbe, 0xe9,
-	0xea, 0x3d, 0x90, 0x3e, 0x48, 0x21, 0x69, 0x08, 0xe0, 0xf5, 0x44, 0xe0, 0xf0, 0x40, 0x89, 0x05,
-	0x97, 0x74, 0xd1, 0xb7, 0xb9, 0x62, 0x7d, 0xea, 0xf2, 0x80, 0x4b, 0x21, 0x49, 0x18, 0x81, 0x02,
-	0x03, 0x17, 0x38, 0x49, 0xf1, 0x1d, 0x4d, 0x0a, 0xba, 0x7d, 0xea, 0x82, 0x0b, 0x19, 0x4a, 0xd3,
-	0xaf, 0x3c, 0xd5, 0xc6, 0x2e, 0x80, 0xeb, 0x71, 0x9a, 0x55, 0xf6, 0x7c, 0x46, 0xa7, 0xf3, 0x88,
-	0x29, 0x01, 0x41, 0xd1, 0xbf, 0x3c, 0x24, 0xf1, 0x67, 0x53, 0x96, 0xb0, 0x3e, 0x2a, 0xfa, 0xc9,
-	0x6d, 0x6e, 0xf6, 0xa0, 0x98, 0xe2, 0xc6, 0x48, 0xaf, 0x85, 0x2c, 0x62, 0xbe, 0x6c, 0xa2, 0x0e,
-	0xea, 0xd6, 0x07, 0xe7, 0xe4, 0x7f, 0x53, 0x72, 0x9f, 0xd1, 0xc3, 0xa3, 0x55, 0x6c, 0x6a, 0xe3,
-	0x22, 0x6b, 0x80, 0x6e, 0x78, 0xe0, 0xbc, 0x30, 0xdb, 0xe3, 0x93, 0xd2, 0x51, 0x36, 0x2b, 0x9d,
-	0x6a, 0xb7, 0x3e, 0x68, 0x91, 0xfc, 0x0a, 0x52, 0x5e, 0x41, 0x46, 0x05, 0x31, 0x3c, 0x4b, 0x87,
-	0x6c, 0x63, 0xb3, 0xb5, 0x64, 0xbe, 0x77, 0x6d, 0xed, 0x8f, 0xb0, 0xde, 0xbf, 0x4c, 0x34, 0x6e,
-	0x94, 0x8d, 0x32, 0x28, 0x0d, 0x47, 0xd7, 0xa7, 0x42, 0xaa, 0x68, 0x22, 0x82, 0x19, 0x34, 0xab,
-	0x99, 0xfa, 0xc5, 0x21, 0xf5, 0x51, 0x9a, 0xb8, 0x0b, 0x66, 0x30, 0x6c, 0xad, 0x62, 0x13, 0x6d,
-	0x63, 0xb3, 0x91, 0x2f, 0xde, 0x8d, 0xb2, 0xc6, 0xc7, 0xd3, 0x5f, 0xea, 0x71, 0xb5, 0xc1, 0x68,
-	0xbd, 0xc1, 0xe8, 0x7b, 0x83, 0xd1, 0x5b, 0x82, 0xb5, 0x75, 0x82, 0xb5, 0xcf, 0x04, 0x6b, 0x4f,
-	0x37, 0xae, 0x50, 0xcf, 0x73, 0x9b, 0x38, 0xe0, 0xd3, 0x62, 0x69, 0xcf, 0x63, 0xb6, 0x2c, 0x0b,
-	0xba, 0xe8, 0x0f, 0xe8, 0xeb, 0xde, 0xb3, 0xa8, 0x65, 0xc8, 0xa5, 0x5d, 0xcb, 0x7e, 0xc4, 0xd5,
-	0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x95, 0x23, 0x1f, 0x59, 0x43, 0x02, 0x00, 0x00,
+	// 392 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0xcd, 0xaa, 0xd3, 0x40,
+	0x14, 0xc7, 0x13, 0x7b, 0xb9, 0x60, 0xee, 0x45, 0xb8, 0x41, 0x21, 0x2d, 0x38, 0x29, 0x01, 0xa5,
+	0x82, 0x9d, 0xb1, 0x75, 0xa7, 0xbb, 0x50, 0x28, 0xee, 0x24, 0xea, 0xc6, 0x4d, 0x98, 0xa4, 0xd3,
+	0x71, 0x30, 0xc9, 0x89, 0x99, 0x49, 0xb1, 0x6f, 0xe1, 0xd2, 0x8d, 0xef, 0xd3, 0x65, 0x97, 0xae,
+	0xa2, 0xb4, 0x6f, 0xd0, 0x27, 0x90, 0x4c, 0x12, 0x5a, 0x29, 0xdc, 0xee, 0x32, 0x9c, 0xdf, 0xff,
+	0xe3, 0x1c, 0x62, 0x8d, 0x41, 0xa6, 0x20, 0x85, 0x24, 0x39, 0x40, 0x32, 0x16, 0x59, 0xcc, 0x32,
+	0x25, 0x56, 0x4c, 0x92, 0xd5, 0x24, 0x62, 0x8a, 0x4e, 0x08, 0x67, 0x19, 0x93, 0x42, 0xe2, 0xbc,
+	0x00, 0x05, 0x36, 0x6a, 0x71, 0x5c, 0xe3, 0x47, 0x1a, 0xb7, 0xf4, 0xe0, 0x31, 0x07, 0x0e, 0x1a,
+	0x25, 0xf5, 0x57, 0xa3, 0x1a, 0x20, 0x0e, 0xc0, 0x13, 0x46, 0xf4, 0x2b, 0x2a, 0x97, 0x64, 0x51,
+	0x16, 0x54, 0x09, 0xc8, 0xda, 0xf9, 0xab, 0x4b, 0x25, 0x4e, 0x92, 0xb4, 0xc2, 0xfb, 0xd5, 0xb3,
+	0x6e, 0xe7, 0x4d, 0xb3, 0x0f, 0x8a, 0x2a, 0x66, 0xcf, 0xac, 0xeb, 0x9c, 0x16, 0x34, 0x95, 0x8e,
+	0x39, 0x34, 0x47, 0x37, 0xd3, 0xe7, 0xf8, 0xfe, 0xa6, 0xf8, 0xbd, 0xa6, 0xfd, 0xab, 0x4d, 0xe5,
+	0x1a, 0x41, 0xab, 0xb5, 0xc1, 0xb2, 0x13, 0x88, 0xbf, 0xd2, 0x28, 0x61, 0x61, 0xd7, 0x51, 0x3a,
+	0x0f, 0x86, 0xbd, 0xd1, 0xcd, 0xb4, 0x8f, 0x9b, 0x2d, 0x70, 0xb7, 0x05, 0x9e, 0xb5, 0x84, 0xff,
+	0xac, 0x36, 0x39, 0x54, 0x6e, 0x7f, 0x4d, 0xd3, 0xe4, 0x8d, 0x77, 0x6e, 0xe1, 0xfd, 0xfc, 0xe3,
+	0x9a, 0xc1, 0x5d, 0x37, 0xe8, 0x84, 0xd2, 0x8e, 0x2d, 0x6b, 0x21, 0xa4, 0x2a, 0x42, 0x91, 0x2d,
+	0xc1, 0xe9, 0xe9, 0xea, 0x2f, 0x2e, 0x55, 0x9f, 0xd5, 0x8a, 0x77, 0xd9, 0x12, 0xfc, 0xfe, 0xa6,
+	0x72, 0xcd, 0x43, 0xe5, 0xde, 0x35, 0xc1, 0x47, 0x2b, 0x2f, 0x78, 0xb8, 0xe8, 0x28, 0xfb, 0x9b,
+	0xf5, 0xa8, 0x76, 0x0a, 0x15, 0x84, 0x9c, 0x96, 0x9c, 0x49, 0xe7, 0x4a, 0x07, 0xbd, 0xbc, 0x78,
+	0x23, 0x80, 0xe4, 0x23, 0xcc, 0xb5, 0xc6, 0x7f, 0xda, 0x66, 0x3d, 0x69, 0xb2, 0xfe, 0x77, 0xf4,
+	0x82, 0xdb, 0xfc, 0x14, 0xfe, 0xb4, 0xd9, 0x21, 0x73, 0xbb, 0x43, 0xe6, 0xdf, 0x1d, 0x32, 0x7f,
+	0xec, 0x91, 0xb1, 0xdd, 0x23, 0xe3, 0xf7, 0x1e, 0x19, 0x9f, 0xdf, 0x72, 0xa1, 0xbe, 0x94, 0x11,
+	0x8e, 0x21, 0x25, 0x6d, 0xfc, 0x38, 0xa1, 0x91, 0xec, 0x1e, 0x64, 0x35, 0x99, 0x92, 0xef, 0x67,
+	0x7f, 0x82, 0x5a, 0xe7, 0x4c, 0x46, 0xd7, 0xfa, 0xf6, 0xaf, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff,
+	0x76, 0x99, 0xc9, 0xcb, 0xb6, 0x02, 0x00, 0x00,
 }
 
 func (m *GenesisState) Marshal() (dAtA []byte, err error) {
@@ -143,6 +154,18 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	_ = i
 	var l int
 	_ = l
+	if m.PoolToGauges != nil {
+		{
+			size, err := m.PoolToGauges.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintGenesis(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x22
+	}
 	if m.DistrInfo != nil {
 		{
 			size, err := m.DistrInfo.MarshalToSizedBuffer(dAtA[:i])
@@ -209,6 +232,10 @@ func (m *GenesisState) Size() (n int) {
 		l = m.DistrInfo.Size()
 		n += 1 + l + sovGenesis(uint64(l))
 	}
+	if m.PoolToGauges != nil {
+		l = m.PoolToGauges.Size()
+		n += 1 + l + sovGenesis(uint64(l))
+	}
 	return n
 }
 
@@ -350,6 +377,42 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error {
 				return err
 			}
 			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field PoolToGauges", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenesis
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenesis
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenesis
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.PoolToGauges == nil {
+				m.PoolToGauges = &PoolToGauges{}
+			}
+			if err := m.PoolToGauges.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipGenesis(dAtA[iNdEx:])
diff --git a/x/pool-incentives/types/incentives.pb.go b/x/pool-incentives/types/incentives.pb.go
index 032861865..cae9214e3 100644
--- a/x/pool-incentives/types/incentives.pb.go
+++ b/x/pool-incentives/types/incentives.pb.go
@@ -209,11 +209,117 @@ func (m *DistrRecord) GetGaugeId() uint64 {
 	return 0
 }
 
+type PoolToGauge struct {
+	PoolId   uint64        `protobuf:"varint,1,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty" yaml:"pool_id"`
+	GaugeId  uint64        `protobuf:"varint,2,opt,name=gauge_id,json=gaugeId,proto3" json:"gauge_id,omitempty" yaml:"gauge"`
+	Duration time.Duration `protobuf:"bytes,3,opt,name=duration,proto3,stdduration" json:"duration" yaml:"duration"`
+}
+
+func (m *PoolToGauge) Reset()         { *m = PoolToGauge{} }
+func (m *PoolToGauge) String() string { return proto.CompactTextString(m) }
+func (*PoolToGauge) ProtoMessage()    {}
+func (*PoolToGauge) Descriptor() ([]byte, []int) {
+	return fileDescriptor_a8153bad03e553d1, []int{4}
+}
+func (m *PoolToGauge) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *PoolToGauge) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_PoolToGauge.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *PoolToGauge) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_PoolToGauge.Merge(m, src)
+}
+func (m *PoolToGauge) XXX_Size() int {
+	return m.Size()
+}
+func (m *PoolToGauge) XXX_DiscardUnknown() {
+	xxx_messageInfo_PoolToGauge.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PoolToGauge proto.InternalMessageInfo
+
+func (m *PoolToGauge) GetPoolId() uint64 {
+	if m != nil {
+		return m.PoolId
+	}
+	return 0
+}
+
+func (m *PoolToGauge) GetGaugeId() uint64 {
+	if m != nil {
+		return m.GaugeId
+	}
+	return 0
+}
+
+func (m *PoolToGauge) GetDuration() time.Duration {
+	if m != nil {
+		return m.Duration
+	}
+	return 0
+}
+
+type PoolToGauges struct {
+	PoolToGauge []PoolToGauge `protobuf:"bytes,2,rep,name=pool_to_gauge,json=poolToGauge,proto3" json:"pool_to_gauge"`
+}
+
+func (m *PoolToGauges) Reset()         { *m = PoolToGauges{} }
+func (m *PoolToGauges) String() string { return proto.CompactTextString(m) }
+func (*PoolToGauges) ProtoMessage()    {}
+func (*PoolToGauges) Descriptor() ([]byte, []int) {
+	return fileDescriptor_a8153bad03e553d1, []int{5}
+}
+func (m *PoolToGauges) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *PoolToGauges) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_PoolToGauges.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *PoolToGauges) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_PoolToGauges.Merge(m, src)
+}
+func (m *PoolToGauges) XXX_Size() int {
+	return m.Size()
+}
+func (m *PoolToGauges) XXX_DiscardUnknown() {
+	xxx_messageInfo_PoolToGauges.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PoolToGauges proto.InternalMessageInfo
+
+func (m *PoolToGauges) GetPoolToGauge() []PoolToGauge {
+	if m != nil {
+		return m.PoolToGauge
+	}
+	return nil
+}
+
 func init() {
 	proto.RegisterType((*Params)(nil), "osmosis.poolincentives.v1beta1.Params")
 	proto.RegisterType((*LockableDurationsInfo)(nil), "osmosis.poolincentives.v1beta1.LockableDurationsInfo")
 	proto.RegisterType((*DistrInfo)(nil), "osmosis.poolincentives.v1beta1.DistrInfo")
 	proto.RegisterType((*DistrRecord)(nil), "osmosis.poolincentives.v1beta1.DistrRecord")
+	proto.RegisterType((*PoolToGauge)(nil), "osmosis.poolincentives.v1beta1.PoolToGauge")
+	proto.RegisterType((*PoolToGauges)(nil), "osmosis.poolincentives.v1beta1.PoolToGauges")
 }
 
 func init() {
@@ -221,37 +327,43 @@ func init() {
 }
 
 var fileDescriptor_a8153bad03e553d1 = []byte{
-	// 466 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x52, 0x41, 0x6b, 0xd4, 0x40,
-	0x18, 0xcd, 0xd4, 0x65, 0x6b, 0x67, 0x0b, 0x62, 0xaa, 0xb8, 0xed, 0x21, 0x29, 0x01, 0xa5, 0x20,
-	0x3b, 0xe3, 0xd6, 0xdb, 0x7a, 0x0b, 0xab, 0xb0, 0xea, 0x41, 0x02, 0x22, 0x78, 0x59, 0x26, 0xc9,
-	0x34, 0x3b, 0x34, 0xc9, 0xb7, 0x64, 0x66, 0x57, 0xfb, 0x0f, 0x04, 0x2f, 0x1e, 0x7b, 0xec, 0x6f,
-	0xf1, 0xd4, 0x63, 0x8f, 0xe2, 0x21, 0xca, 0xee, 0xc5, 0xf3, 0xfe, 0x02, 0xc9, 0x64, 0x42, 0x83,
-	0x05, 0xa1, 0xa7, 0xcc, 0xcb, 0xcb, 0x7b, 0xdf, 0x7b, 0xf3, 0x05, 0x3f, 0x03, 0x99, 0x81, 0x14,
-	0x92, 0xce, 0x01, 0xd2, 0x81, 0xc8, 0x23, 0x9e, 0x2b, 0xb1, 0xe4, 0x92, 0x2e, 0x87, 0x21, 0x57,
-	0x6c, 0x48, 0xaf, 0x5f, 0x91, 0x79, 0x01, 0x0a, 0x6c, 0xc7, 0x28, 0x48, 0xa5, 0x68, 0xb1, 0x46,
-	0x70, 0xf0, 0x20, 0x81, 0x04, 0xf4, 0xa7, 0xb4, 0x3a, 0xd5, 0xaa, 0x03, 0x27, 0x01, 0x48, 0x52,
-	0x4e, 0x35, 0x0a, 0x17, 0x27, 0x34, 0x5e, 0x14, 0x4c, 0x09, 0xc8, 0x6b, 0xde, 0x7b, 0x8d, 0xbb,
-	0xef, 0x58, 0xc1, 0x32, 0x69, 0x8f, 0xf0, 0x6e, 0x26, 0x72, 0xc5, 0xe3, 0x69, 0xcc, 0x73, 0xc8,
-	0xfa, 0xe8, 0x10, 0x1d, 0xed, 0xf8, 0x8f, 0x36, 0xa5, 0xbb, 0x77, 0xc6, 0xb2, 0x74, 0xe4, 0xb5,
-	0x59, 0x2f, 0xe8, 0xd5, 0x70, 0x5c, 0xa1, 0x51, 0xe7, 0xfc, 0xc2, 0xb5, 0xbc, 0x2f, 0x08, 0x3f,
-	0x7c, 0x0b, 0xd1, 0x29, 0x0b, 0x53, 0x3e, 0x36, 0x63, 0xe4, 0x24, 0x3f, 0x01, 0x1b, 0xb0, 0x9d,
-	0x1a, 0x62, 0xda, 0x04, 0x90, 0x7d, 0x74, 0x78, 0xe7, 0xa8, 0x77, 0xbc, 0x4f, 0xea, 0x88, 0xa4,
-	0x89, 0x48, 0x1a, 0xad, 0xff, 0xf8, 0xb2, 0x74, 0xad, 0x4d, 0xe9, 0xee, 0xd7, 0x01, 0x6e, 0x5a,
-	0x78, 0xe7, 0xbf, 0x5c, 0x14, 0xdc, 0x4f, 0xff, 0x1d, 0xea, 0x7d, 0x47, 0x78, 0x67, 0x2c, 0xa4,
-	0x2a, 0xf4, 0xf8, 0x19, 0xde, 0x55, 0xa0, 0x58, 0x3a, 0xfd, 0xc4, 0x45, 0x32, 0x53, 0xa6, 0xda,
-	0xcb, 0xca, 0xfd, 0x67, 0xe9, 0x3e, 0x49, 0x84, 0x9a, 0x2d, 0x42, 0x12, 0x41, 0x46, 0x23, 0x7d,
-	0xc9, 0xe6, 0x31, 0x90, 0xf1, 0x29, 0x55, 0x67, 0x73, 0x2e, 0xc9, 0x24, 0x57, 0xd7, 0x17, 0xd1,
-	0xf6, 0xf2, 0x82, 0x9e, 0x86, 0x1f, 0x34, 0xb2, 0xdf, 0xe0, 0xed, 0x82, 0x47, 0x50, 0xc4, 0xb2,
-	0xbf, 0xa5, 0xdb, 0x3d, 0x25, 0xff, 0x5f, 0x1b, 0xd1, 0x29, 0x03, 0xad, 0xf1, 0x3b, 0x55, 0xa2,
-	0xa0, 0x71, 0xf0, 0xbe, 0x22, 0xdc, 0x6b, 0xd1, 0x36, 0xc1, 0x77, 0x13, 0xb6, 0x48, 0xf8, 0x54,
-	0xc4, 0xba, 0x42, 0xc7, 0xdf, 0xdb, 0x94, 0xee, 0xbd, 0x3a, 0x54, 0xc3, 0x78, 0xc1, 0xb6, 0x3e,
-	0x4e, 0x62, 0xfb, 0x15, 0xee, 0x9a, 0xc2, 0x5b, 0xba, 0x30, 0xb9, 0x5d, 0xe1, 0xc0, 0xa8, 0x47,
-	0x9d, 0x3f, 0x17, 0x2e, 0xf2, 0xdf, 0x5f, 0xae, 0x1c, 0x74, 0xb5, 0x72, 0xd0, 0xef, 0x95, 0x83,
-	0xbe, 0xad, 0x1d, 0xeb, 0x6a, 0xed, 0x58, 0x3f, 0xd6, 0x8e, 0xf5, 0xf1, 0x45, 0xcb, 0xcf, 0xb4,
-	0x1d, 0xa4, 0x2c, 0x94, 0x0d, 0xa0, 0xcb, 0xe1, 0x31, 0xfd, 0x7c, 0xe3, 0x4f, 0xd7, 0x83, 0xc2,
-	0xae, 0x5e, 0xfb, 0xf3, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x40, 0x59, 0x91, 0x1c, 0x11, 0x03,
-	0x00, 0x00,
+	// 565 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0xbf, 0x6f, 0xd3, 0x40,
+	0x14, 0xc7, 0x73, 0x6d, 0x94, 0xb4, 0xe7, 0xf0, 0xcb, 0x05, 0x91, 0x16, 0xc9, 0x8e, 0x4e, 0x02,
+	0x55, 0x8a, 0x62, 0x93, 0xb2, 0x85, 0x2d, 0x0a, 0xa0, 0x00, 0x43, 0x65, 0x51, 0x21, 0xb1, 0x44,
+	0x76, 0x7c, 0x75, 0xac, 0xda, 0x7e, 0x91, 0xef, 0x12, 0xe8, 0x7f, 0x80, 0xc4, 0xc2, 0xd8, 0xb1,
+	0xff, 0x07, 0x1b, 0x53, 0xc7, 0x8e, 0x88, 0xc1, 0xa0, 0x64, 0x61, 0xce, 0x5f, 0x80, 0x7c, 0x3e,
+	0x93, 0x13, 0x95, 0x2a, 0x98, 0x72, 0xcf, 0xef, 0xbe, 0xef, 0x7d, 0xbe, 0xef, 0x9d, 0x82, 0x1f,
+	0x03, 0x8b, 0x81, 0x85, 0xcc, 0x9e, 0x02, 0x44, 0x9d, 0x30, 0x19, 0xd3, 0x84, 0x87, 0x73, 0xca,
+	0xec, 0x79, 0xd7, 0xa3, 0xdc, 0xed, 0xda, 0xeb, 0x4f, 0xd6, 0x34, 0x05, 0x0e, 0xba, 0x21, 0x15,
+	0x56, 0xae, 0x50, 0xb2, 0x52, 0xb0, 0x77, 0x37, 0x80, 0x00, 0xc4, 0x55, 0x3b, 0x3f, 0x15, 0xaa,
+	0x3d, 0x23, 0x00, 0x08, 0x22, 0x6a, 0x8b, 0xc8, 0x9b, 0x1d, 0xdb, 0xfe, 0x2c, 0x75, 0x79, 0x08,
+	0x49, 0x91, 0x27, 0x2f, 0x71, 0xed, 0xd0, 0x4d, 0xdd, 0x98, 0xe9, 0x3d, 0xdc, 0x88, 0xc3, 0x84,
+	0x53, 0x7f, 0xe4, 0xd3, 0x04, 0xe2, 0x26, 0x6a, 0xa1, 0xfd, 0xed, 0xfe, 0xfd, 0x55, 0x66, 0xee,
+	0x9c, 0xba, 0x71, 0xd4, 0x23, 0x6a, 0x96, 0x38, 0x5a, 0x11, 0x0e, 0xf2, 0xa8, 0x57, 0x3d, 0x3b,
+	0x37, 0x2b, 0xe4, 0x23, 0xc2, 0xf7, 0x5e, 0xc3, 0xf8, 0xc4, 0xf5, 0x22, 0x3a, 0x90, 0x6d, 0xd8,
+	0x30, 0x39, 0x06, 0x1d, 0xb0, 0x1e, 0xc9, 0xc4, 0xa8, 0x04, 0x60, 0x4d, 0xd4, 0xda, 0xdc, 0xd7,
+	0x0e, 0x76, 0xad, 0x02, 0xd1, 0x2a, 0x11, 0xad, 0x52, 0xdb, 0x7f, 0x78, 0x91, 0x99, 0x95, 0x55,
+	0x66, 0xee, 0x16, 0x00, 0x57, 0x4b, 0x90, 0xb3, 0x1f, 0x26, 0x72, 0xee, 0x44, 0x7f, 0x37, 0x25,
+	0x5f, 0x11, 0xde, 0x1e, 0x84, 0x8c, 0xa7, 0xa2, 0xfd, 0x04, 0x37, 0x38, 0x70, 0x37, 0x1a, 0xbd,
+	0xa7, 0x61, 0x30, 0xe1, 0xd2, 0xda, 0xb3, 0xbc, 0xfa, 0xf7, 0xcc, 0x7c, 0x14, 0x84, 0x7c, 0x32,
+	0xf3, 0xac, 0x31, 0xc4, 0xf6, 0x58, 0x0c, 0x59, 0xfe, 0x74, 0x98, 0x7f, 0x62, 0xf3, 0xd3, 0x29,
+	0x65, 0xd6, 0x30, 0xe1, 0xeb, 0x41, 0xa8, 0xb5, 0x88, 0xa3, 0x89, 0xf0, 0xad, 0x88, 0xf4, 0x57,
+	0xb8, 0x9e, 0xd2, 0x31, 0xa4, 0x3e, 0x6b, 0x6e, 0x08, 0x77, 0x6d, 0xeb, 0xfa, 0xb5, 0x59, 0x82,
+	0xd2, 0x11, 0x9a, 0x7e, 0x35, 0x27, 0x72, 0xca, 0x0a, 0xe4, 0x13, 0xc2, 0x9a, 0x92, 0xd6, 0x2d,
+	0xbc, 0x15, 0xb8, 0xb3, 0x80, 0x8e, 0x42, 0x5f, 0x58, 0xa8, 0xf6, 0x77, 0x56, 0x99, 0x79, 0xab,
+	0x80, 0x2a, 0x33, 0xc4, 0xa9, 0x8b, 0xe3, 0xd0, 0xd7, 0x9f, 0xe3, 0x9a, 0x34, 0xbc, 0x21, 0x0c,
+	0x5b, 0xff, 0x67, 0xd8, 0x91, 0xea, 0x5e, 0xf5, 0xd7, 0xb9, 0x89, 0xc8, 0x17, 0x84, 0xb5, 0x43,
+	0x80, 0xe8, 0x0d, 0xbc, 0xc8, 0xeb, 0xeb, 0x6d, 0x5c, 0xcf, 0x2d, 0xad, 0x61, 0xf4, 0x55, 0x66,
+	0xde, 0x2c, 0x60, 0x64, 0x82, 0x38, 0xb5, 0xfc, 0x34, 0xf4, 0xf5, 0xb6, 0x82, 0xbe, 0x21, 0x6e,
+	0xdf, 0x5e, 0x65, 0x66, 0x43, 0x41, 0x57, 0xb8, 0x1d, 0xbc, 0x55, 0x6e, 0xb8, 0xb9, 0xd9, 0x42,
+	0xd7, 0xbf, 0x91, 0x07, 0xf2, 0x8d, 0xc8, 0x31, 0x94, 0xc2, 0xe2, 0x65, 0xfc, 0xa9, 0x43, 0x28,
+	0x6e, 0x28, 0xf0, 0x4c, 0x3f, 0xc2, 0x37, 0x04, 0x24, 0x87, 0x91, 0x68, 0xfb, 0xaf, 0xeb, 0x52,
+	0x8a, 0xc8, 0x75, 0x69, 0x53, 0xe5, 0xd3, 0xd1, 0xc5, 0xc2, 0x40, 0x97, 0x0b, 0x03, 0xfd, 0x5c,
+	0x18, 0xe8, 0xf3, 0xd2, 0xa8, 0x5c, 0x2e, 0x8d, 0xca, 0xb7, 0xa5, 0x51, 0x79, 0xf7, 0x54, 0x19,
+	0xba, 0xec, 0xd1, 0x89, 0x5c, 0x8f, 0x95, 0x81, 0x3d, 0xef, 0x1e, 0xd8, 0x1f, 0xae, 0xfc, 0x1d,
+	0x88, 0x6d, 0x78, 0x35, 0xe1, 0xfb, 0xc9, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x23, 0xa8, 0x43,
+	0xbb, 0x36, 0x04, 0x00, 0x00,
 }
 
 func (this *DistrRecord) Equal(that interface{}) bool {
@@ -431,6 +543,84 @@ func (m *DistrRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
+func (m *PoolToGauge) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *PoolToGauge) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *PoolToGauge) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	n1, err1 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.Duration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.Duration):])
+	if err1 != nil {
+		return 0, err1
+	}
+	i -= n1
+	i = encodeVarintIncentives(dAtA, i, uint64(n1))
+	i--
+	dAtA[i] = 0x1a
+	if m.GaugeId != 0 {
+		i = encodeVarintIncentives(dAtA, i, uint64(m.GaugeId))
+		i--
+		dAtA[i] = 0x10
+	}
+	if m.PoolId != 0 {
+		i = encodeVarintIncentives(dAtA, i, uint64(m.PoolId))
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *PoolToGauges) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *PoolToGauges) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *PoolToGauges) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.PoolToGauge) > 0 {
+		for iNdEx := len(m.PoolToGauge) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.PoolToGauge[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintIncentives(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	return len(dAtA) - i, nil
+}
+
 func encodeVarintIncentives(dAtA []byte, offset int, v uint64) int {
 	offset -= sovIncentives(v)
 	base := offset
@@ -501,6 +691,38 @@ func (m *DistrRecord) Size() (n int) {
 	return n
 }
 
+func (m *PoolToGauge) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.PoolId != 0 {
+		n += 1 + sovIncentives(uint64(m.PoolId))
+	}
+	if m.GaugeId != 0 {
+		n += 1 + sovIncentives(uint64(m.GaugeId))
+	}
+	l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.Duration)
+	n += 1 + l + sovIncentives(uint64(l))
+	return n
+}
+
+func (m *PoolToGauges) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if len(m.PoolToGauge) > 0 {
+		for _, e := range m.PoolToGauge {
+			l = e.Size()
+			n += 1 + l + sovIncentives(uint64(l))
+		}
+	}
+	return n
+}
+
 func sovIncentives(x uint64) (n int) {
 	return (math_bits.Len64(x|1) + 6) / 7
 }
@@ -894,6 +1116,211 @@ func (m *DistrRecord) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *PoolToGauge) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowIncentives
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: PoolToGauge: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: PoolToGauge: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field PoolId", wireType)
+			}
+			m.PoolId = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowIncentives
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.PoolId |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 2:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field GaugeId", wireType)
+			}
+			m.GaugeId = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowIncentives
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.GaugeId |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Duration", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowIncentives
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthIncentives
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthIncentives
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.Duration, dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipIncentives(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthIncentives
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *PoolToGauges) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowIncentives
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: PoolToGauges: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: PoolToGauges: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field PoolToGauge", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowIncentives
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthIncentives
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthIncentives
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.PoolToGauge = append(m.PoolToGauge, PoolToGauge{})
+			if err := m.PoolToGauge[len(m.PoolToGauge)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipIncentives(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthIncentives
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func skipIncentives(dAtA []byte) (n int, err error) {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/x/streamswap/README.md b/x/streamswap/README.md
new file mode 100644
index 000000000..e3e3817d0
--- /dev/null
+++ b/x/streamswap/README.md
@@ -0,0 +1,121 @@
+# Streamswap
+
+## Abstract
+
+Streamswap is a new way and innovative way of selling token sale.
+The mechanism allows anyone to create a new Sale event and sell any
+amount of tokens in a more democratic way than the traditional solutions.
+
+## Context
+
+Since the first ICO boom, token sale mechanism was one of the driving
+force for web3 onboarding.
+Promise of a cheap tokens which can quickly accrue value is very attractive
+for casual any sort of investors. Easy way of fundrising (the funding team)
+opened doors for cohorts of new teams to focus on building on web3.
+
+Traditional mechanisms of token sale included:
+
+- Automated ICO, where team decides about the issuance price and the sale
+  happens through a swap controlled by a smart contract.
+- Regulated, centralized ICO - token sale controlled by a dedicated company,
+  which will preform all operations using centralized services meeting
+  regulatory requirements (KYC...). Example: Coinlist sales.
+- Balancer style ICO: a novel solution to utilize Dutch Auction mechanism to
+  find a fair strike price.
+
+The first two mechanisms are not well suited for early stage startups, where
+the token sale price is usually defined by a founding team and can't be
+impacted by the ecosystem wisdom. False marketing actions are usually setup
+to support their initial price.
+
+The latter mechanism is not democratic - big entities can control the
+price movements or place big orders leaving smaller investors with nothing.
+
+## Design
+
+### Sale Creation
+
+[Sale](https://github.com/osmosis-labs/osmosis/blob/main/proto/osmosis/streamswap/v1/state.proto#L11) object represent a particular token sale event and describes the main
+required parameters guarding the sale process:
+
+- `treasury`: address where the sale earnings will go. When the sale is over,
+  anyone can trigger a [`MsgFinalizeSale`](https://github.com/osmosis-labs/osmosis/blob/main/proto/osmosis/streamswap/v1/tx.proto#L42)
+  to clean up the sale state and move the earning to the treasury.
+- `id`: unique identifier of the sale.
+- `token_out`: denom to sale (distributed to the investors).
+  Also known as a base currency.
+- `token_in`: payment denom - used to buy `token_out`.
+  Also known as quote currency.
+- `token_out_supply`: total initial supply of `token_in` to sale.
+- `start_time`: Unix timestamp when the sale starts.
+- `end_time`: Unix timestamp when the sale ends.
+- `name`: Name of the sale.
+- `url`: an external resource describing a sale. Can be IPFS link or a
+  commonwealth post.
+
+The `Sale` object contains also other internal attributes to describe the current
+status of the sale.
+
+Anyone can create a `Sale` by sending [`MsgCreateSale`](https://github.com/osmosis-labs/osmosis/blob/robert%2Fstreamswap-spec/proto/osmosis/streamswap/v1/tx.proto#L21) transaction.
+When doing so, `token_out` amount of `Sale.token_out` tokens will be debited from
+his account and escrowed in the module account to distribute to Sale investors.
+Moreover the creator will be charged `sale_creation_fee` (module param) and the
+fee will be transferred to `sale_creation_fee_recipient` (module param) account.
+This fee is not recoverable.
+
+See other [module parameters](https://github.com/osmosis-labs/osmosis/main/proto/osmosis/streamswap/v1/params.proto) which control the sale creation.
+
+### Investing and distribution mechanism
+
+Anyone can join a sale by sending a [MsgSubscribe](https://github.com/osmosis-labs/osmosis/blob/main/proto/osmosis/streamswap/v1/tx.proto#L13) transaction.
+When doing so, the transaction author has to specify the `amount` he wants to spend in the sale.
+That `amount` will be credited from tx author and pledged to the sale.
+
+`MsgSubscribe` can be submitted at any time after the sale is created and before it's end time.
+
+From that moment, the investor will join the **token sale distribution stream**:
+
+- The distribution happens discretely in rounds. Each round is 1 second long.
+  We define: `total_rounds := (sale.end_time - sale.start_time) / round_duration`.
+- During each round we stream `round_supply := sale.token_out_supply / total_rounds`.
+- During each round each investor receives `round_supply * current_investor_pledge / total_remaining_sale_pledge` share of the `token_out`.
+
+At any time an investor can increase his participation for the sale by sending again `MsgSubscribe`
+(his pledge will increase accordingly) or cancel it by sending
+[`MsgWithdraw`](https://github.com/osmosis-labs/osmosis/blob/main/proto/osmosis/streamswap/v1/tx.proto#32).
+When cancelling, the module will send back unspent pledged tokens to the investor
+and keep the purchased tokens until the sale end_time.
+
+### Withdrawing purchased tokens
+
+When participating in a sale, investors receives a stream of sale tokens.
+These tokens are locked until sale end to avoid second market creating during
+the sale. Once sale is finished (block time is after `sale.end_time`), every
+investor can send [`MsgExitSale`](https://github.com/osmosis-labs/osmosis/blob/main/proto/osmosis/streamswap/v1/tx.proto#L37)
+to close his position and withdraw purchased tokens to his account.
+
+### Withdrawing sale proceedings
+
+To withdraw earned token to the `sale.treasury` account anyone can send a
+transaction with [`MsgFinalizeSale`](https://github.com/osmosis-labs/osmosis/blob/main/proto/osmosis/streamswap/v1/tx.proto#L42) after the `sale.end_time`.
+This transaction will send `sale.income` tokens from the module escrow account
+to the `sale.treasury` and set `sale.finalized = true`.
+
+### Events
+
+The module uses typed events. Please see [`event.proto`](https://github.com/osmosis-labs/osmosis/blob/main/proto/osmosis/streamswap/v1/event.proto)
+to inspect list events.
+
+## Consequences
+
+- The new sale mechanism provides a truly democratic way for token distribution and sale.
+- It can be easily integrated with AMM pools: proceedings from the sale can
+  automatically be pledged to AMM.
+- After the sale ends, there are few algorithms to provide token indicative market price:
+  - average price: `sale.income / sale.token_out_supply`
+  - last streamed price: `round_supply(last_round) / (sale.token_out_supply / total_rounds)`
+
+## Future directions
+
+- providing incentive for sales with `OSMO` or `ATOM` used as a base currency.
diff --git a/x/streamswap/types/tx.pb.go b/x/streamswap/types/tx.pb.go
index 6a816fce7..eb6eaacc5 100644
--- a/x/streamswap/types/tx.pb.go
+++ b/x/streamswap/types/tx.pb.go
@@ -471,12 +471,12 @@ type MsgClient interface {
 	// User can do it any time unless his deposit is empty.
 	Withdraw(ctx context.Context, in *MsgWithdraw, opts ...grpc.CallOption) (*emptypb.Empty, error)
 	// ExitSale withdraws (by a user who subscribed to the sale) purchased
-	// tokens_out from the pool and remained tokens_in. Must be called before
+	// tokens_out from the pool and remained tokens_in. Must be called after
 	// the sale end.
 	ExitSale(ctx context.Context, in *MsgExitSale, opts ...grpc.CallOption) (*MsgExitSaleResponse, error)
 	// FinalizeSale clean ups the sale and sends income (earned tokens_in) to the
-	// Sale recipient. Returns error if called before the Sale end. Anyone can
-	// call this method.
+	// Sale recipient. Returns error if called before the Sale end or it was
+	// already finalized. Anyone can call this method.
 	FinalizeSale(ctx context.Context, in *MsgFinalizeSale, opts ...grpc.CallOption) (*MsgFinalizeSaleResponse, error)
 }
 
@@ -551,12 +551,12 @@ type MsgServer interface {
 	// User can do it any time unless his deposit is empty.
 	Withdraw(context.Context, *MsgWithdraw) (*emptypb.Empty, error)
 	// ExitSale withdraws (by a user who subscribed to the sale) purchased
-	// tokens_out from the pool and remained tokens_in. Must be called before
+	// tokens_out from the pool and remained tokens_in. Must be called after
 	// the sale end.
 	ExitSale(context.Context, *MsgExitSale) (*MsgExitSaleResponse, error)
 	// FinalizeSale clean ups the sale and sends income (earned tokens_in) to the
-	// Sale recipient. Returns error if called before the Sale end. Anyone can
-	// call this method.
+	// Sale recipient. Returns error if called before the Sale end or it was
+	// already finalized. Anyone can call this method.
 	FinalizeSale(context.Context, *MsgFinalizeSale) (*MsgFinalizeSaleResponse, error)
 }
 
diff --git a/x/superfluid/module.go b/x/superfluid/module.go
index 3bda20318..a4d6703e5 100644
--- a/x/superfluid/module.go
+++ b/x/superfluid/module.go
@@ -43,8 +43,7 @@ var (
 // ----------------------------------------------------------------------------
 
 // AppModuleBasic implements the AppModuleBasic interface for the capability module.
-type AppModuleBasic struct {
-}
+type AppModuleBasic struct{}
 
 func NewAppModuleBasic() AppModuleBasic {
 	return AppModuleBasic{}
diff --git a/x/tokenfactory/module.go b/x/tokenfactory/module.go
index d9a266c7a..a892be013 100644
--- a/x/tokenfactory/module.go
+++ b/x/tokenfactory/module.go
@@ -41,8 +41,7 @@ var (
 // ----------------------------------------------------------------------------
 
 // AppModuleBasic implements the AppModuleBasic interface for the capability module.
-type AppModuleBasic struct {
-}
+type AppModuleBasic struct{}
 
 func NewAppModuleBasic() AppModuleBasic {
 	return AppModuleBasic{}
diff --git a/x/twap/api.go b/x/twap/api.go
index 829e95d80..202497567 100644
--- a/x/twap/api.go
+++ b/x/twap/api.go
@@ -62,7 +62,8 @@ func (k Keeper) GetArithmeticTwapToNow(
 	poolId uint64,
 	baseAssetDenom string,
 	quoteAssetDenom string,
-	startTime time.Time) (sdk.Dec, error) {
+	startTime time.Time,
+) (sdk.Dec, error) {
 	if startTime.After(ctx.BlockTime()) {
 		return sdk.Dec{}, types.StartTimeAfterEndTimeError{StartTime: startTime, EndTime: ctx.BlockTime()}
 	}
@@ -78,7 +79,7 @@ func (k Keeper) GetArithmeticTwapToNow(
 	return computeArithmeticTwap(startRecord, endRecord, quoteAssetDenom)
 }
 
-// GetCurrentAccumulatorRecord returns a TwapRecord struct corresponding to the state of pool `poolId`
+// GetBeginBlockAccumulatorRecord returns a TwapRecord struct corresponding to the state of pool `poolId`
 // as of the beginning of the block this is called on.
 // This uses the state of the beginning of the block, as if there were swaps since the block has started,
 // these swaps have had no time to be arbitraged back.
diff --git a/x/twap/client/grpc/grpc_query.go b/x/twap/client/grpc/grpc_query.go
index 2ad40e0ed..82dce7285 100644
--- a/x/twap/client/grpc/grpc_query.go
+++ b/x/twap/client/grpc/grpc_query.go
@@ -20,15 +20,6 @@ type Querier struct {
 
 var _ queryproto.QueryServer = Querier{}
 
-func (q Querier) Params(grpcCtx context.Context,
-	req *queryproto.ParamsRequest,
-) (*queryproto.ParamsResponse, error) {
-	if req == nil {
-		return nil, status.Error(codes.InvalidArgument, "empty request")
-	}
-	ctx := sdk.UnwrapSDKContext(grpcCtx)
-	return q.Q.Params(ctx, *req)
-}
 func (q Querier) ArithmeticTwap(grpcCtx context.Context,
 	req *queryproto.ArithmeticTwapRequest,
 ) (*queryproto.ArithmeticTwapResponse, error) {
@@ -47,3 +38,12 @@ func (q Querier) ArithmeticTwapToNow(grpcCtx context.Context,
 	ctx := sdk.UnwrapSDKContext(grpcCtx)
 	return q.Q.ArithmeticTwapToNow(ctx, *req)
 }
+func (q Querier) Params(grpcCtx context.Context,
+	req *queryproto.ParamsRequest,
+) (*queryproto.ParamsResponse, error) {
+	if req == nil {
+		return nil, status.Error(codes.InvalidArgument, "empty request")
+	}
+	ctx := sdk.UnwrapSDKContext(grpcCtx)
+	return q.Q.Params(ctx, *req)
+}
diff --git a/x/twap/logic.go b/x/twap/logic.go
index 4a2bf7494..f90328fe3 100644
--- a/x/twap/logic.go
+++ b/x/twap/logic.go
@@ -36,8 +36,13 @@ func newTwapRecord(k types.AmmInterface, ctx sdk.Context, poolId uint64, denom0,
 // returns spot prices for both pairs of assets, and the 'latest error time'.
 // The latest error time is the previous time if there is no error in getting spot prices.
 // if there is an error in getting spot prices, then the latest error time is ctx.Blocktime()
-func getSpotPrices(ctx sdk.Context, k types.AmmInterface, poolId uint64, denom0, denom1 string, previousErrorTime time.Time) (
-	sp0 sdk.Dec, sp1 sdk.Dec, latestErrTime time.Time) {
+func getSpotPrices(
+	ctx sdk.Context,
+	k types.AmmInterface,
+	poolId uint64,
+	denom0, denom1 string,
+	previousErrorTime time.Time,
+) (sp0 sdk.Dec, sp1 sdk.Dec, latestErrTime time.Time) {
 	latestErrTime = previousErrorTime
 	sp0, err0 := k.CalculateSpotPrice(ctx, poolId, denom0, denom1)
 	sp1, err1 := k.CalculateSpotPrice(ctx, poolId, denom1, denom0)
@@ -101,18 +106,16 @@ func (k Keeper) EndBlock(ctx sdk.Context) {
 // with updated spot prices and spot price errors, if any.
 // Returns nil on success.
 // Returns error if:
-// - fails to get previous records.
-// - fails to get denoms from the pool.
-// - the number of records does not match expected relative to the
-//  number of denoms in the pool.
+//   - fails to get previous records.
+//   - fails to get denoms from the pool.
+//   - the number of records does not match expected relative to the
+//     number of denoms in the pool.
 func (k Keeper) updateRecords(ctx sdk.Context, poolId uint64) error {
 	// Will only err if pool doesn't have most recent entry set
 	records, err := k.getAllMostRecentRecordsForPool(ctx, poolId)
 	if err != nil {
 		return err
 	}
-	// TODO: Add a safety assert, that # of records is as we expect, given # of denoms in the pool
-	// namely, that for `k` denoms in pool, there should be k * (k - 1) / 2 records
 
 	denoms, err := k.ammkeeper.GetPoolDenoms(ctx, poolId)
 	if err != nil {
diff --git a/x/twap/migrate.go b/x/twap/migrate.go
index 8e08f7cf0..920ce0cf4 100644
--- a/x/twap/migrate.go
+++ b/x/twap/migrate.go
@@ -6,8 +6,8 @@ import (
 
 // MigrateExistingPools iterates through all pools and creates state entry for the twap module.
 func (k Keeper) MigrateExistingPools(ctx sdk.Context, latestPoolId uint64) error {
-	for i := 1; i <= int(latestPoolId); i++ {
-		err := k.afterCreatePool(ctx, uint64(i))
+	for i := uint64(1); i <= latestPoolId; i++ {
+		err := k.afterCreatePool(ctx, i)
 		if err != nil {
 			return err
 		}
diff --git a/x/twap/store.go b/x/twap/store.go
index 3f6a06d8a..70f159c3f 100644
--- a/x/twap/store.go
+++ b/x/twap/store.go
@@ -20,6 +20,9 @@ func (e timeTooOldError) Error() string {
 		" Try storing the accumulator value. (requested time %s)", e.Time)
 }
 
+// just has to not be empty, for store to work / not register as a delete.
+var sentinelExistsValue = []byte{1}
+
 // trackChangedPool places an entry into a transient store,
 // to track that this pool changed this block.
 // This tracking is for use in EndBlock, to create new TWAP records.
@@ -27,8 +30,7 @@ func (k Keeper) trackChangedPool(ctx sdk.Context, poolId uint64) {
 	store := ctx.TransientStore(k.transientKey)
 	poolIdBz := make([]byte, 8)
 	binary.LittleEndian.PutUint64(poolIdBz, poolId)
-	// just has to not be empty, for store to work / not register as a delete.
-	sentinelExistsValue := []byte{1}
+
 	store.Set(poolIdBz, sentinelExistsValue)
 }
 
@@ -100,7 +102,8 @@ func (k Keeper) pruneRecordsBeforeTimeButNewest(ctx sdk.Context, lastKeptTime ti
 		poolKey := uniqueTriplet{
 			poolId: twapToRemove.PoolId,
 			asset0: twapToRemove.Asset0Denom,
-			asset1: twapToRemove.Asset1Denom}
+			asset1: twapToRemove.Asset1Denom,
+		}
 		_, hasSeenPoolRecord := seenPoolAssetTriplets[poolKey]
 		if !hasSeenPoolRecord {
 			seenPoolAssetTriplets[poolKey] = struct{}{}
diff --git a/x/twap/types/expected_interfaces.go b/x/twap/types/expected_interfaces.go
index 0af93f752..8ed0bfe4e 100644
--- a/x/twap/types/expected_interfaces.go
+++ b/x/twap/types/expected_interfaces.go
@@ -9,8 +9,10 @@ type AmmInterface interface {
 	// using the specified pool.
 	// E.g. if pool 1 traded 2 atom for 3 osmo, the quote asset was atom, and the base asset was osmo,
 	// this would return 1.5. (Meaning that 1 atom costs 1.5 osmo)
-	CalculateSpotPrice(ctx sdk.Context,
+	CalculateSpotPrice(
+		ctx sdk.Context,
 		poolID uint64,
 		baseAssetDenom string,
-		quoteAssetDenom string) (price sdk.Dec, err error)
+		quoteAssetDenom string,
+	) (price sdk.Dec, err error)
 }
diff --git a/x/twap/types/twapmock/amminterface.go b/x/twap/types/twapmock/amminterface.go
index aac5d0bb9..95adfb4db 100644
--- a/x/twap/types/twapmock/amminterface.go
+++ b/x/twap/types/twapmock/amminterface.go
@@ -52,7 +52,8 @@ func (p *ProgrammedAmmInterface) ProgramPoolDenomsOverride(poolId uint64, overri
 }
 
 func (p *ProgrammedAmmInterface) ProgramPoolSpotPriceOverride(poolId uint64,
-	baseDenom, quoteDenom string, overrideSp sdk.Dec, overrideErr error) {
+	baseDenom, quoteDenom string, overrideSp sdk.Dec, overrideErr error,
+) {
 	input := SpotPriceInput{poolId, baseDenom, quoteDenom}
 	p.programmedSpotPrice[input] = SpotPriceResult{overrideSp, overrideErr}
 }
@@ -71,7 +72,8 @@ func (p *ProgrammedAmmInterface) GetPoolDenoms(ctx sdk.Context, poolId uint64) (
 func (p *ProgrammedAmmInterface) CalculateSpotPrice(ctx sdk.Context,
 	poolId uint64,
 	baseDenom,
-	quoteDenom string) (price sdk.Dec, err error) {
+	quoteDenom string,
+) (price sdk.Dec, err error) {
 	input := SpotPriceInput{poolId, baseDenom, quoteDenom}
 	if res, ok := p.programmedSpotPrice[input]; ok {
 		return res.Sp, res.Err
diff --git a/x/twap/types/utils.go b/x/twap/types/utils.go
index de393a27e..18ce483ec 100644
--- a/x/twap/types/utils.go
+++ b/x/twap/types/utils.go
@@ -13,8 +13,9 @@ import (
 var MaxSpotPrice = sdk.NewDec(2).Power(128).Sub(sdk.OneDec())
 
 // GetAllUniqueDenomPairs returns all unique pairs of denoms, where for every pair
-// (X, Y), X >= Y.
+// (X, Y), X > Y.
 // The pair (X,Y) should only appear once in the list
+// Panics if finds duplicate pairs.
 //
 // NOTE: Sorts the input denoms slice.
 func GetAllUniqueDenomPairs(denoms []string) ([]string, []string) {
diff --git a/x/txfees/module.go b/x/txfees/module.go
index 0d75448e0..5eaa0feb9 100644
--- a/x/txfees/module.go
+++ b/x/txfees/module.go
@@ -42,8 +42,7 @@ const ModuleName = types.ModuleName
 // ----------------------------------------------------------------------------
 
 // AppModuleBasic implements the AppModuleBasic interface for the txfees module.
-type AppModuleBasic struct {
-}
+type AppModuleBasic struct{}
 
 func NewAppModuleBasic() AppModuleBasic {
 	return AppModuleBasic{}


Test patch:
diff --git a/app/apptesting/test_suite.go b/app/apptesting/test_suite.go
index 70babd22e..71446fecf 100644
--- a/app/apptesting/test_suite.go
+++ b/app/apptesting/test_suite.go
@@ -215,9 +215,10 @@ func (s *KeeperTestHelper) BeginNewBlockWithProposer(executeNextEpoch bool, prop
 
 	fmt.Println("beginning block ", s.Ctx.BlockHeight())
 	s.App.BeginBlocker(s.Ctx, reqBeginBlock)
+	s.Ctx = s.App.NewContext(false, reqBeginBlock.Header)
 }
 
-// EndBlock ends the block.
+// EndBlock ends the block, and runs commit
 func (s *KeeperTestHelper) EndBlock() {
 	reqEndBlock := abci.RequestEndBlock{Height: s.Ctx.BlockHeight()}
 	s.App.EndBlocker(s.Ctx, reqEndBlock)
diff --git a/osmomath/decimal_test.go b/osmomath/decimal_test.go
index f02e581cb..b5441930e 100644
--- a/osmomath/decimal_test.go
+++ b/osmomath/decimal_test.go
@@ -7,6 +7,7 @@ import (
 	"math/big"
 	"testing"
 
+	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
 	"gopkg.in/yaml.v2"
@@ -68,9 +69,9 @@ func (s *decimalTestSuite) TestNewDecFromStr() {
 		{"0.75", false, NewDecWithPrec(75, 2)},
 		{"0.8", false, NewDecWithPrec(8, 1)},
 		{"0.11111", false, NewDecWithPrec(11111, 5)},
-		{"314460551102969.3144278234343371835", true, NewBigDec(3141203149163817869)},
+		{"314460551102969.31442782343433718353144278234343371835", true, NewBigDec(3141203149163817869)},
 		{
-			"314460551102969314427823434337.1835718092488231350",
+			"314460551102969314427823434337.18357180924882313501835718092488231350",
 			true, NewDecFromBigIntWithPrec(largeBigInt, 4),
 		},
 		{
@@ -83,7 +84,7 @@ func (s *decimalTestSuite) TestNewDecFromStr() {
 		{"foobar", true, BigDec{}},
 		{"0.foobar", true, BigDec{}},
 		{"0.foobar.", true, BigDec{}},
-		{"23258839177459420497578361852416145099316523541994177929007686373780457219628733546438113622840434097944400691400517693873107252115668992", true, BigDec{}},
+		{"179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216", true, BigDec{}},
 	}
 
 	for tcIndex, tc := range tests {
@@ -112,14 +113,15 @@ func (s *decimalTestSuite) TestDecString() {
 		d    BigDec
 		want string
 	}{
-		{NewBigDec(0), "0.000000000000000000"},
-		{NewBigDec(1), "1.000000000000000000"},
-		{NewBigDec(10), "10.000000000000000000"},
-		{NewBigDec(12340), "12340.000000000000000000"},
-		{NewDecWithPrec(12340, 4), "1.234000000000000000"},
-		{NewDecWithPrec(12340, 5), "0.123400000000000000"},
-		{NewDecWithPrec(12340, 8), "0.000123400000000000"},
-		{NewDecWithPrec(1009009009009009009, 17), "10.090090090090090090"},
+		{NewBigDec(0), "0.000000000000000000000000000000000000"},
+		{NewBigDec(1), "1.000000000000000000000000000000000000"},
+		{NewBigDec(10), "10.000000000000000000000000000000000000"},
+		{NewBigDec(12340), "12340.000000000000000000000000000000000000"},
+		{NewDecWithPrec(12340, 4), "1.234000000000000000000000000000000000"},
+		{NewDecWithPrec(12340, 5), "0.123400000000000000000000000000000000"},
+		{NewDecWithPrec(12340, 8), "0.000123400000000000000000000000000000"},
+		{NewDecWithPrec(1009009009009009009, 17), "10.090090090090090090000000000000000000"},
+		{MustNewDecFromStr("10.090090090090090090090090090090090090"), "10.090090090090090090090090090090090090"},
 	}
 	for tcIndex, tc := range tests {
 		s.Require().Equal(tc.want, tc.d.String(), "bad String(), index: %v", tcIndex)
@@ -148,6 +150,56 @@ func (s *decimalTestSuite) TestDecFloat64() {
 	}
 }
 
+func (s *decimalTestSuite) TestSdkDec() {
+	tests := []struct {
+		d    BigDec
+		want sdk.Dec
+		expPanic bool
+	}{
+		{NewBigDec(0), sdk.MustNewDecFromStr("0.000000000000000000"), false},
+		{NewBigDec(1), sdk.MustNewDecFromStr("1.000000000000000000"), false},
+		{NewBigDec(10), sdk.MustNewDecFromStr("10.000000000000000000"), false},
+		{NewBigDec(12340), sdk.MustNewDecFromStr("12340.000000000000000000"), false},
+		{NewDecWithPrec(12340, 4), sdk.MustNewDecFromStr("1.234000000000000000"), false},
+		{NewDecWithPrec(12340, 5), sdk.MustNewDecFromStr("0.123400000000000000"), false},
+		{NewDecWithPrec(12340, 8), sdk.MustNewDecFromStr("0.000123400000000000"), false},
+		{NewDecWithPrec(1009009009009009009, 17), sdk.MustNewDecFromStr("10.090090090090090090"), false},
+	}
+	for tcIndex, tc := range tests {
+		if tc.expPanic {
+			s.Require().Panics(func() { tc.d.SdkDec() })
+		} else {
+			value := tc.d.SdkDec()
+			s.Require().Equal(tc.want, value, "bad SdkDec(), index: %v", tcIndex)
+		}
+	}
+}
+
+func (s *decimalTestSuite) TestBigDecFromSdkDec() {
+	tests := []struct {
+		d    sdk.Dec
+		want BigDec
+		expPanic bool
+	}{
+		{sdk.MustNewDecFromStr("0.000000000000000000"), NewBigDec(0), false},
+		{sdk.MustNewDecFromStr("1.000000000000000000"), NewBigDec(1), false},
+		{sdk.MustNewDecFromStr("10.000000000000000000"), NewBigDec(10), false},
+		{sdk.MustNewDecFromStr("12340.000000000000000000"), NewBigDec(12340), false},
+		{sdk.MustNewDecFromStr("1.234000000000000000"), NewDecWithPrec(12340, 4), false},
+		{sdk.MustNewDecFromStr("0.123400000000000000"), NewDecWithPrec(12340, 5), false},
+		{sdk.MustNewDecFromStr("0.000123400000000000"), NewDecWithPrec(12340, 8), false},
+		{sdk.MustNewDecFromStr("10.090090090090090090"), NewDecWithPrec(1009009009009009009, 17), false},
+	}
+	for tcIndex, tc := range tests {
+		if tc.expPanic {
+			s.Require().Panics(func() { BigDecFromSdkDec(tc.d) })
+		} else {
+			value := BigDecFromSdkDec(tc.d)
+			s.Require().Equal(tc.want, value, "bad BigDecFromSdkDec(), index: %v", tcIndex)
+		}
+	}
+}
+
 func (s *decimalTestSuite) TestEqualities() {
 	tests := []struct {
 		d1, d2     BigDec
@@ -227,7 +279,7 @@ func (s *decimalTestSuite) TestArithmetic() {
 
 		{
 			NewBigDec(3), NewBigDec(7), NewBigDec(21), NewBigDec(21),
-			NewDecWithPrec(428571428571428571, 18), NewDecWithPrec(428571428571428572, 18), NewDecWithPrec(428571428571428571, 18),
+			MustNewDecFromStr("0.428571428571428571428571428571428571"), MustNewDecFromStr("0.428571428571428571428571428571428572"), MustNewDecFromStr("0.428571428571428571428571428571428571"),
 			NewBigDec(10), NewBigDec(-4),
 		},
 		{
@@ -243,7 +295,7 @@ func (s *decimalTestSuite) TestArithmetic() {
 		},
 		{
 			NewDecWithPrec(3333, 4), NewDecWithPrec(333, 4), NewDecWithPrec(1109889, 8), NewDecWithPrec(1109889, 8),
-			MustNewDecFromStr("10.009009009009009009"), MustNewDecFromStr("10.009009009009009010"), MustNewDecFromStr("10.009009009009009009"),
+			MustNewDecFromStr("10.009009009009009009009009009009009009"), MustNewDecFromStr("10.009009009009009009009009009009009010"), MustNewDecFromStr("10.009009009009009009009009009009009009"),
 			NewDecWithPrec(3666, 4), NewDecWithPrec(3, 1),
 		},
 	}
@@ -336,7 +388,7 @@ func (s *decimalTestSuite) TestStringOverflow() {
 	s.Require().NoError(err)
 	dec3 := dec1.Add(dec2)
 	s.Require().Equal(
-		"19844653375691057515930281852116324640.000000000000000000",
+		"19844653375691057515930281852116324640.000000000000000000000000000000000000",
 		dec3.String(),
 	)
 }
@@ -363,14 +415,14 @@ func (s *decimalTestSuite) TestDecCeil() {
 		input    BigDec
 		expected BigDec
 	}{
-		{NewDecWithPrec(1000000000000000, Precision), NewBigDec(1)}, // 0.001 => 1.0
-		{NewDecWithPrec(-1000000000000000, Precision), ZeroDec()},   // -0.001 => 0.0
+		{MustNewDecFromStr("0.001"), NewBigDec(1)}, // 0.001 => 1.0
+		{MustNewDecFromStr("-0.001"), ZeroDec()},   // -0.001 => 0.0
 		{ZeroDec(), ZeroDec()}, // 0.0 => 0.0
-		{NewDecWithPrec(900000000000000000, Precision), NewBigDec(1)},    // 0.9 => 1.0
-		{NewDecWithPrec(4001000000000000000, Precision), NewBigDec(5)},   // 4.001 => 5.0
-		{NewDecWithPrec(-4001000000000000000, Precision), NewBigDec(-4)}, // -4.001 => -4.0
-		{NewDecWithPrec(4700000000000000000, Precision), NewBigDec(5)},   // 4.7 => 5.0
-		{NewDecWithPrec(-4700000000000000000, Precision), NewBigDec(-4)}, // -4.7 => -4.0
+		{MustNewDecFromStr("0.9"), NewBigDec(1)},    // 0.9 => 1.0
+		{MustNewDecFromStr("4.001"), NewBigDec(5)},   // 4.001 => 5.0
+		{MustNewDecFromStr("-4.001"), NewBigDec(-4)}, // -4.001 => -4.0
+		{MustNewDecFromStr("4.7"), NewBigDec(5)},   // 4.7 => 5.0
+		{MustNewDecFromStr("-4.7"), NewBigDec(-4)}, // -4.7 => -4.0
 	}
 
 	for i, tc := range testCases {
@@ -390,7 +442,7 @@ func (s *decimalTestSuite) TestPower() {
 		{NewDecWithPrec(2, 1), 2, NewDecWithPrec(4, 2)},                        // 0.2 ^ 2 => 0.04
 		{NewDecFromInt(NewInt(3)), 3, NewDecFromInt(NewInt(27))},               // 3 ^ 3 => 27
 		{NewDecFromInt(NewInt(-3)), 4, NewDecFromInt(NewInt(81))},              // -3 ^ 4 = 81
-		{NewDecWithPrec(1414213562373095049, 18), 2, NewDecFromInt(NewInt(2))}, // 1.414213562373095049 ^ 2 = 2
+		{MustNewDecFromStr("1.414213562373095048801688724209698079"), 2, NewDecFromInt(NewInt(2))}, // 1.414213562373095048801688724209698079 ^ 2 = 2
 	}
 
 	for i, tc := range testCases {
@@ -410,11 +462,11 @@ func (s *decimalTestSuite) TestApproxRoot() {
 		{NewDecWithPrec(4, 2), 2, NewDecWithPrec(2, 1)},                                // 0.04 ^ (0.5) => 0.2
 		{NewDecFromInt(NewInt(27)), 3, NewDecFromInt(NewInt(3))},                       // 27 ^ (1/3) => 3
 		{NewDecFromInt(NewInt(-81)), 4, NewDecFromInt(NewInt(-3))},                     // -81 ^ (0.25) => -3
-		{NewDecFromInt(NewInt(2)), 2, NewDecWithPrec(1414213562373095049, 18)},         // 2 ^ (0.5) => 1.414213562373095049
-		{NewDecWithPrec(1005, 3), 31536000, MustNewDecFromStr("1.000000000158153904")}, // 1.005 ^ (1/31536000) ≈ 1.00000000016
-		{SmallestDec(), 2, NewDecWithPrec(1, 9)},                                       // 1e-18 ^ (0.5) => 1e-9
-		{SmallestDec(), 3, MustNewDecFromStr("0.000000999999999997")},                  // 1e-18 ^ (1/3) => 1e-6
-		{NewDecWithPrec(1, 8), 3, MustNewDecFromStr("0.002154434690031900")},           // 1e-8 ^ (1/3) ≈ 0.00215443469
+		{NewDecFromInt(NewInt(2)), 2, MustNewDecFromStr("1.414213562373095048801688724209698079")},         // 2 ^ (0.5) => 1.414213562373095048801688724209698079
+		{NewDecWithPrec(1005, 3), 31536000, MustNewDecFromStr("1.000000000158153903837946258002096839")}, // 1.005 ^ (1/31536000) ≈ 1.000000000158153903837946258002096839
+		{SmallestDec(), 2, NewDecWithPrec(1, 18)},                                       // 1e-36 ^ (0.5) => 1e-18
+		{SmallestDec(), 3, MustNewDecFromStr("0.000000000001000000000000000002431786")},                  // 1e-36 ^ (1/3) => 1e-12
+		{NewDecWithPrec(1, 8), 3, MustNewDecFromStr("0.002154434690031883721759293566519280")},           // 1e-8 ^ (1/3) ≈ 0.002154434690031883721759293566519
 	}
 
 	// In the case of 1e-8 ^ (1/3), the result repeats every 5 iterations starting from iteration 24
@@ -438,7 +490,7 @@ func (s *decimalTestSuite) TestApproxSqrt() {
 		{NewDecWithPrec(4, 2), NewDecWithPrec(2, 1)},                        // 0.09 => 0.3
 		{NewDecFromInt(NewInt(9)), NewDecFromInt(NewInt(3))},                // 9 => 3
 		{NewDecFromInt(NewInt(-9)), NewDecFromInt(NewInt(-3))},              // -9 => -3
-		{NewDecFromInt(NewInt(2)), NewDecWithPrec(1414213562373095049, 18)}, // 2 => 1.414213562373095049
+		{NewDecFromInt(NewInt(2)), MustNewDecFromStr("1.414213562373095048801688724209698079")}, // 2 => 1.414213562373095048801688724209698079
 	}
 
 	for i, tc := range testCases {
@@ -453,24 +505,24 @@ func (s *decimalTestSuite) TestDecSortableBytes() {
 		d    BigDec
 		want []byte
 	}{
-		{NewBigDec(0), []byte("000000000000000000.000000000000000000")},
-		{NewBigDec(1), []byte("000000000000000001.000000000000000000")},
-		{NewBigDec(10), []byte("000000000000000010.000000000000000000")},
-		{NewBigDec(12340), []byte("000000000000012340.000000000000000000")},
-		{NewDecWithPrec(12340, 4), []byte("000000000000000001.234000000000000000")},
-		{NewDecWithPrec(12340, 5), []byte("000000000000000000.123400000000000000")},
-		{NewDecWithPrec(12340, 8), []byte("000000000000000000.000123400000000000")},
-		{NewDecWithPrec(1009009009009009009, 17), []byte("000000000000000010.090090090090090090")},
-		{NewDecWithPrec(-1009009009009009009, 17), []byte("-000000000000000010.090090090090090090")},
-		{NewBigDec(1000000000000000000), []byte("max")},
-		{NewBigDec(-1000000000000000000), []byte("--")},
+		{NewBigDec(0), []byte("000000000000000000000000000000000000.000000000000000000000000000000000000")},
+		{NewBigDec(1), []byte("000000000000000000000000000000000001.000000000000000000000000000000000000")},
+		{NewBigDec(10), []byte("000000000000000000000000000000000010.000000000000000000000000000000000000")},
+		{NewBigDec(12340), []byte("000000000000000000000000000000012340.000000000000000000000000000000000000")},
+		{NewDecWithPrec(12340, 4), []byte("000000000000000000000000000000000001.234000000000000000000000000000000000")},
+		{NewDecWithPrec(12340, 5), []byte("000000000000000000000000000000000000.123400000000000000000000000000000000")},
+		{NewDecWithPrec(12340, 8), []byte("000000000000000000000000000000000000.000123400000000000000000000000000000")},
+		{NewDecWithPrec(1009009009009009009, 17), []byte("000000000000000000000000000000000010.090090090090090090000000000000000000")},
+		{NewDecWithPrec(-1009009009009009009, 17), []byte("-000000000000000000000000000000000010.090090090090090090000000000000000000")},
+		{MustNewDecFromStr("1000000000000000000000000000000000000"), []byte("max")},
+		{MustNewDecFromStr("-1000000000000000000000000000000000000"), []byte("--")},
 	}
 	for tcIndex, tc := range tests {
 		s.Require().Equal(tc.want, SortableDecBytes(tc.d), "bad String(), index: %v", tcIndex)
 	}
 
-	s.Require().Panics(func() { SortableDecBytes(NewBigDec(1000000000000000001)) })
-	s.Require().Panics(func() { SortableDecBytes(NewBigDec(-1000000000000000001)) })
+	s.Require().Panics(func() { SortableDecBytes(MustNewDecFromStr("1000000000000000000000000000000000001")) })
+	s.Require().Panics(func() { SortableDecBytes(MustNewDecFromStr("-1000000000000000000000000000000000001")) })
 }
 
 func (s *decimalTestSuite) TestDecEncoding() {
@@ -482,32 +534,32 @@ func (s *decimalTestSuite) TestDecEncoding() {
 	}{
 		{
 			NewBigDec(0), "30",
-			"\"0.000000000000000000\"",
-			"\"0.000000000000000000\"\n",
+			"\"0.000000000000000000000000000000000000\"",
+			"\"0.000000000000000000000000000000000000\"\n",
 		},
 		{
 			NewDecWithPrec(4, 2),
-			"3430303030303030303030303030303030",
-			"\"0.040000000000000000\"",
-			"\"0.040000000000000000\"\n",
+			"3430303030303030303030303030303030303030303030303030303030303030303030",
+			"\"0.040000000000000000000000000000000000\"",
+			"\"0.040000000000000000000000000000000000\"\n",
 		},
 		{
 			NewDecWithPrec(-4, 2),
-			"2D3430303030303030303030303030303030",
-			"\"-0.040000000000000000\"",
-			"\"-0.040000000000000000\"\n",
+			"2D3430303030303030303030303030303030303030303030303030303030303030303030",
+			"\"-0.040000000000000000000000000000000000\"",
+			"\"-0.040000000000000000000000000000000000\"\n",
 		},
 		{
-			NewDecWithPrec(1414213562373095049, 18),
-			"31343134323133353632333733303935303439",
-			"\"1.414213562373095049\"",
-			"\"1.414213562373095049\"\n",
+			MustNewDecFromStr("1.414213562373095048801688724209698079"),
+			"31343134323133353632333733303935303438383031363838373234323039363938303739",
+			"\"1.414213562373095048801688724209698079\"",
+			"\"1.414213562373095048801688724209698079\"\n",
 		},
 		{
-			NewDecWithPrec(-1414213562373095049, 18),
-			"2D31343134323133353632333733303935303439",
-			"\"-1.414213562373095049\"",
-			"\"-1.414213562373095049\"\n",
+			MustNewDecFromStr("-1.414213562373095048801688724209698079"),
+			"2D31343134323133353632333733303935303438383031363838373234323039363938303739",
+			"\"-1.414213562373095048801688724209698079\"",
+			"\"-1.414213562373095048801688724209698079\"\n",
 		},
 	}
 
diff --git a/osmomath/int_test.go b/osmomath/int_test.go
index 33fe5dbc7..a3e376a95 100644
--- a/osmomath/int_test.go
+++ b/osmomath/int_test.go
@@ -40,17 +40,17 @@ func (s *intTestSuite) TestFromUint64() {
 }
 
 func (s *intTestSuite) TestIntPanic() {
-	// Max Int = 2^512-1 = 1.340781e+154
-	// Min Int = -(2^512-1) = -1.340781e+154
-	s.Require().NotPanics(func() { NewIntWithDecimal(4, 153) })
-	i1 := NewIntWithDecimal(5, 153)
-	s.Require().NotPanics(func() { NewIntWithDecimal(5, 153) })
-	i2 := NewIntWithDecimal(6, 153)
-	s.Require().NotPanics(func() { NewIntWithDecimal(6, 153) })
-	i3 := NewIntWithDecimal(7, 153)
-
-	s.Require().Panics(func() { NewIntWithDecimal(2, 154) })
-	s.Require().Panics(func() { NewIntWithDecimal(9, 160) })
+	// Max Int = 2^1024-1 = 8.988466e+308
+	// Min Int = -(2^1024-1) = -8.988466e+308
+	s.Require().NotPanics(func() { NewIntWithDecimal(4, 307) })
+	i1 := NewIntWithDecimal(4, 307)
+	s.Require().NotPanics(func() { NewIntWithDecimal(5, 307) })
+	i2 := NewIntWithDecimal(5, 307)
+	s.Require().NotPanics(func() { NewIntWithDecimal(92, 306) })
+	i3 := NewIntWithDecimal(92, 306)
+
+	s.Require().Panics(func() { NewIntWithDecimal(2, 308) })
+	s.Require().Panics(func() { NewIntWithDecimal(9, 340) })
 
 	// Overflow check
 	s.Require().NotPanics(func() { i1.Add(i1) })
@@ -84,7 +84,7 @@ func (s *intTestSuite) TestIntPanic() {
 	s.Require().Panics(func() { i3.Mul(i3.Neg()) })
 
 	// Bound check
-	intmax := NewIntFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(512), nil), big.NewInt(1)))
+	intmax := NewIntFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(1024), nil), big.NewInt(1)))
 	intmin := intmax.Neg()
 	s.Require().NotPanics(func() { intmax.Add(ZeroInt()) })
 	s.Require().NotPanics(func() { intmin.Sub(ZeroInt()) })
diff --git a/osmomath/sigfig_round_test.go b/osmomath/sigfig_round_test.go
new file mode 100644
index 000000000..a62225de4
--- /dev/null
+++ b/osmomath/sigfig_round_test.go
@@ -0,0 +1,94 @@
+package osmomath
+
+import (
+	"fmt"
+	"testing"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/osmosis-labs/osmosis/v12/app/apptesting/osmoassert"
+	gammtypes "github.com/osmosis-labs/osmosis/v12/x/gamm/types"
+)
+
+func TestSigFigRound(t *testing.T) {
+	// sigfig = 8
+	tenToSigFig := gammtypes.SpotPriceSigFigs
+
+	testCases := []struct {
+		name           string
+		decimal        sdk.Dec
+		tenToSigFig    sdk.Int
+		expectedResult sdk.Dec
+	}{
+		{
+			name:           "Zero decimal",
+			decimal:        sdk.ZeroDec(),
+			tenToSigFig:    tenToSigFig,
+			expectedResult: sdk.ZeroDec(),
+		},
+		{
+			name:           "Zero tenToSigFig",
+			decimal:        sdk.MustNewDecFromStr("2.123"),
+			tenToSigFig:    sdk.ZeroInt(),
+			expectedResult: sdk.ZeroDec(),
+		},
+		// With input, decimal >= 0.1. We have:
+		// 	- dTimesK = 63.045
+		// 	- k = 0
+		// Applying the formula, we have:
+		//  - numerator = (dTimesK  * tenToSigFig).RoundInt() = 6304500000
+		//  - denominator = tenToSigFig * 10^k = 100000000
+		//  - result = numerator / denominator = 63
+		{
+			name:           "Big decimal, default tenToSigFig",
+			decimal:        sdk.MustNewDecFromStr("63.045"),
+			tenToSigFig:    tenToSigFig,
+			expectedResult: sdk.MustNewDecFromStr("63.045"),
+		},
+		// With input, decimal < 0.1. We have:
+		// 	- dTimesK = 0.86724
+		// 	- k = 1
+		// Applying the formula, we have:
+		//  - numerator = (dTimesK  * tenToSigFig).RoundInt() = 86724596
+		//  - denominator = tenToSigFig * 10^k = 1000000000
+		//  - result = numerator / denominator = 0.086724
+		{
+			name:           "Small decimal, default tenToSigFig",
+			decimal:        sdk.MustNewDecFromStr("0.0867245957"),
+			tenToSigFig:    tenToSigFig,
+			expectedResult: sdk.MustNewDecFromStr("0.086724596"),
+		},
+		// With input, decimal < 0.1. We have:
+		// 	- dTimesK = 0.86724
+		// 	- k = 1
+		// Applying the formula, we have:
+		//  - numerator = (dTimesK  * tenToSigFig).RoundInt() = 87
+		//  - denominator = tenToSigFig * 10^k = 1000
+		//  - result = numerator / denominator = 0.087
+		{
+			name:           "Small decimal, random tenToSigFig",
+			decimal:        sdk.MustNewDecFromStr("0.086724"),
+			tenToSigFig:    sdk.NewInt(100),
+			expectedResult: sdk.MustNewDecFromStr("0.087"),
+		},
+	}
+
+	for i, tc := range testCases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			var actualResult sdk.Dec
+			osmoassert.ConditionalPanic(t, tc.tenToSigFig.Equal(sdk.ZeroInt()), func() {
+				actualResult = SigFigRound(tc.decimal, tc.tenToSigFig)
+				require.Equal(
+					t,
+					tc.expectedResult,
+					actualResult,
+					fmt.Sprintf("test %d failed: expected value & actual value should be equal", i),
+				)
+			})
+		})
+
+	}
+}
diff --git a/osmoutils/module_account_test.go b/osmoutils/module_account_test.go
new file mode 100644
index 000000000..0b5cfea03
--- /dev/null
+++ b/osmoutils/module_account_test.go
@@ -0,0 +1,67 @@
+package osmoutils_test
+
+import (
+	"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/types/address"
+	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+
+	"github.com/osmosis-labs/osmosis/v12/app/apptesting/osmoassert"
+	"github.com/osmosis-labs/osmosis/v12/osmoutils"
+)
+
+func (s *TestSuite) TestCreateModuleAccount() {
+	baseWithAddr := func(addr sdk.AccAddress) authtypes.AccountI {
+		acc := authtypes.ProtoBaseAccount()
+		acc.SetAddress(addr)
+		return acc
+	}
+	userAccViaSeqnum := func(addr sdk.AccAddress) authtypes.AccountI {
+		base := baseWithAddr(addr)
+		base.SetSequence(2)
+		return base
+	}
+	userAccViaPubkey := func(addr sdk.AccAddress) authtypes.AccountI {
+		base := baseWithAddr(addr)
+		base.SetPubKey(secp256k1.GenPrivKey().PubKey())
+		return base
+	}
+	defaultModuleAccAddr := address.Module("dummy module", []byte{1})
+	testcases := map[string]struct {
+		priorAccounts []authtypes.AccountI
+		moduleAccAddr sdk.AccAddress
+		expErr        bool
+	}{
+		"no prior acc": {
+			priorAccounts: []authtypes.AccountI{},
+			moduleAccAddr: defaultModuleAccAddr,
+			expErr:        false,
+		},
+		"prior empty acc at addr": {
+			priorAccounts: []authtypes.AccountI{baseWithAddr(defaultModuleAccAddr)},
+			moduleAccAddr: defaultModuleAccAddr,
+			expErr:        false,
+		},
+		"prior user acc at addr (sequence)": {
+			priorAccounts: []authtypes.AccountI{userAccViaSeqnum(defaultModuleAccAddr)},
+			moduleAccAddr: defaultModuleAccAddr,
+			expErr:        true,
+		},
+		"prior user acc at addr (pubkey)": {
+			priorAccounts: []authtypes.AccountI{userAccViaPubkey(defaultModuleAccAddr)},
+			moduleAccAddr: defaultModuleAccAddr,
+			expErr:        true,
+		},
+	}
+	for name, tc := range testcases {
+		s.Run(name, func() {
+			s.SetupTest()
+			for _, priorAcc := range tc.priorAccounts {
+				s.App.AccountKeeper.SetAccount(s.Ctx, priorAcc)
+			}
+			err := osmoutils.CreateModuleAccount(s.Ctx, s.App.AccountKeeper, tc.moduleAccAddr)
+			osmoassert.ConditionalError(s.T(), tc.expErr, err)
+		})
+	}
+
+}
diff --git a/osmoutils/store_helper_test.go b/osmoutils/store_helper_test.go
index 802d6289c..bbae8526f 100644
--- a/osmoutils/store_helper_test.go
+++ b/osmoutils/store_helper_test.go
@@ -20,6 +20,11 @@ type TestSuite struct {
 	store sdk.KVStore
 }
 
+func (suite *TestSuite) SetupTest() {
+	suite.Setup()
+
+}
+
 const (
 	keyA               = "a"
 	keyB               = "b"
@@ -69,7 +74,6 @@ func mockStop(b []byte) bool {
 }
 
 func (s *TestSuite) TestGatherAllKeysFromStore() {
-
 	testcases := map[string]struct {
 		preSetKeys     []string
 		expectedValues []string
@@ -315,7 +319,6 @@ func (s *TestSuite) TestGatherValuesFromStorePrefix() {
 }
 
 func (s *TestSuite) TestGetFirstValueAfterPrefixInclusive() {
-
 	testcases := map[string]struct {
 		prefix     []byte
 		preSetKeys []string
@@ -420,7 +423,6 @@ func (s *TestSuite) TestGetFirstValueAfterPrefixInclusive() {
 }
 
 func (s *TestSuite) TestGatherValuesFromIteratorWithStop() {
-
 	testcases := map[string]struct {
 		// if prefix is set, startValue and endValue are ignored.
 		// we either create an iterator prefix or a range iterator.
@@ -549,7 +551,6 @@ func (s *TestSuite) TestGatherValuesFromIteratorWithStop() {
 }
 
 func (s *TestSuite) TestGetIterValuesWithStop() {
-
 	testcases := map[string]struct {
 		preSetKeys []string
 		keyStart   []byte
@@ -659,7 +660,6 @@ func (s *TestSuite) TestGetIterValuesWithStop() {
 }
 
 func (s *TestSuite) TestGetValuesUntilDerivedStop() {
-
 	testcases := map[string]struct {
 		preSetKeys []string
 		keyStart   []byte
@@ -735,7 +735,6 @@ func (s *TestSuite) TestNoStopFn_AlwaysFalse() {
 // TestMustGet tests that MustGet retrieves the correct
 // values from the store and panics if an error is encountered.
 func (s *TestSuite) TestMustGet() {
-
 	tests := map[string]struct {
 		// keys and values to preset
 		preSetKeyValues map[string]proto.Message
@@ -875,7 +874,6 @@ func (s *TestSuite) TestMustSet() {
 // TestMustGetDec tests that MustGetDec retrieves the correct
 // decimal values from the store and panics if an error is encountered.
 func (s *TestSuite) TestMustGetDec() {
-
 	tests := map[string]struct {
 		// keys and values to preset
 		preSetKeyValues map[string]sdk.Dec
diff --git a/tests/e2e/configurer/chain/queries.go b/tests/e2e/configurer/chain/queries.go
index c7f10a82f..8c3600be6 100644
--- a/tests/e2e/configurer/chain/queries.go
+++ b/tests/e2e/configurer/chain/queries.go
@@ -156,7 +156,6 @@ func (n *NodeConfig) QueryArithmeticTwapToNow(poolId uint64, baseAsset, quoteAss
 		"quote_asset", quoteAsset,
 		"start_time", startTime.Format(time.RFC3339Nano),
 	)
-
 	if err != nil {
 		return sdk.Dec{}, err
 	}
@@ -178,7 +177,6 @@ func (n *NodeConfig) QueryArithmeticTwap(poolId uint64, baseAsset, quoteAsset st
 		"start_time", startTime.Format(time.RFC3339Nano),
 		"end_time", endTime.Format(time.RFC3339Nano),
 	)
-
 	if err != nil {
 		return sdk.Dec{}, err
 	}
diff --git a/tests/e2e/containers/config.go b/tests/e2e/containers/config.go
index 12b1d9fde..2ae1e21a9 100644
--- a/tests/e2e/containers/config.go
+++ b/tests/e2e/containers/config.go
@@ -23,11 +23,11 @@ const (
 	// Pre-upgrade osmosis repo/tag to pull.
 	// It should be uploaded to Docker Hub. OSMOSIS_E2E_SKIP_UPGRADE should be unset
 	// for this functionality to be used.
-	previousVersionOsmoRepository = "osmolabs/osmosis-dev"
-	previousVersionOsmoTag        = "v10.0.0-debug"
+	previousVersionOsmoRepository = "osmolabs/osmosis"
+	previousVersionOsmoTag        = "11"
 	// Pre-upgrade repo/tag for osmosis initialization (this should be one version below upgradeVersion)
 	previousVersionInitRepository = "osmolabs/osmosis-e2e-init-chain"
-	previousVersionInitTag        = "v10.0.0-e2e-v4"
+	previousVersionInitTag        = "v11.0.0-e2e-v1"
 	// Hermes repo/version for relayer
 	relayerRepository = "osmolabs/hermes"
 	relayerTag        = "0.13.0"
diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go
index 3abe73981..6ab20e3bc 100644
--- a/tests/e2e/e2e_test.go
+++ b/tests/e2e/e2e_test.go
@@ -152,18 +152,19 @@ func (s *IntegrationTestSuite) TestAddToExistingLock() {
 // to wait for at least the twap keep time.
 func (s *IntegrationTestSuite) TestTWAP() {
 	const (
-		poolFile   = "nativeDenomPool.json"
+		poolFile   = "nativeDenomThreeAssetPool.json"
 		walletName = "swap-exact-amount-in-wallet"
 
-		denomOne = "stake"
-		denomTwo = "uosmo"
+		denomA = "stake"
+		denomB = "uion"
+		denomC = "uosmo"
 
 		minAmountOut = "1"
 
 		epochIdentifier = "day"
 	)
 
-	var coinIn = fmt.Sprintf("20000%s", denomOne)
+	coinAIn, coinBIn, coinCIn := fmt.Sprintf("2000000%s", denomA), fmt.Sprintf("2000000%s", denomB), fmt.Sprintf("2000000%s", denomC)
 
 	chainA := s.configurer.GetChainConfig(0)
 	chainANode, err := chainA.GetDefaultNode()
@@ -179,20 +180,34 @@ func (s *IntegrationTestSuite) TestTWAP() {
 	chainA.WaitForNumHeights(1)
 
 	s.T().Log("querying for the first TWAP to now before swap")
-	twapFromBeforeSwapToBeforeSwapOne, err := chainANode.QueryArithmeticTwapToNow(poolId, denomOne, denomTwo, timeBeforeSwap)
+	twapFromBeforeSwapToBeforeSwapOneAB, err := chainANode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap)
+	s.Require().NoError(err)
+	twapFromBeforeSwapToBeforeSwapOneBC, err := chainANode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap)
+	s.Require().NoError(err)
+	twapFromBeforeSwapToBeforeSwapOneCA, err := chainANode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap)
 	s.Require().NoError(err)
 
-	chainANode.BankSend(coinIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr)
+	chainANode.BankSend(coinAIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr)
+	chainANode.BankSend(coinBIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr)
+	chainANode.BankSend(coinCIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr)
 
 	s.T().Log("querying for the second TWAP to now before swap, must equal to first")
-	twapFromBeforeSwapToBeforeSwapTwo, err := chainANode.QueryArithmeticTwapToNow(poolId, denomOne, denomTwo, timeBeforeSwap.Add(50*time.Millisecond))
+	twapFromBeforeSwapToBeforeSwapTwoAB, err := chainANode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap.Add(50*time.Millisecond))
+	s.Require().NoError(err)
+	twapFromBeforeSwapToBeforeSwapTwoBC, err := chainANode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap.Add(50*time.Millisecond))
+	s.Require().NoError(err)
+	twapFromBeforeSwapToBeforeSwapTwoCA, err := chainANode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap.Add(50*time.Millisecond))
 	s.Require().NoError(err)
 
 	// Since there were no swaps between the two queries, the TWAPs should be the same.
-	osmoassert.DecApproxEq(s.T(), twapFromBeforeSwapToBeforeSwapOne, twapFromBeforeSwapToBeforeSwapTwo, sdk.NewDecWithPrec(1, 3))
+	osmoassert.DecApproxEq(s.T(), twapFromBeforeSwapToBeforeSwapOneAB, twapFromBeforeSwapToBeforeSwapTwoAB, sdk.NewDecWithPrec(1, 3))
+	osmoassert.DecApproxEq(s.T(), twapFromBeforeSwapToBeforeSwapOneBC, twapFromBeforeSwapToBeforeSwapTwoBC, sdk.NewDecWithPrec(1, 3))
+	osmoassert.DecApproxEq(s.T(), twapFromBeforeSwapToBeforeSwapOneCA, twapFromBeforeSwapToBeforeSwapTwoCA, sdk.NewDecWithPrec(1, 3))
 
-	s.T().Log("performing swap")
-	chainANode.SwapExactAmountIn(coinIn, minAmountOut, fmt.Sprintf("%d", poolId), denomTwo, swapWalletAddr)
+	s.T().Log("performing swaps")
+	chainANode.SwapExactAmountIn(coinAIn, minAmountOut, fmt.Sprintf("%d", poolId), denomB, swapWalletAddr)
+	chainANode.SwapExactAmountIn(coinBIn, minAmountOut, fmt.Sprintf("%d", poolId), denomC, swapWalletAddr)
+	chainANode.SwapExactAmountIn(coinCIn, minAmountOut, fmt.Sprintf("%d", poolId), denomA, swapWalletAddr)
 
 	keepPeriodCountDown := time.NewTimer(initialization.TWAPPruningKeepPeriod)
 
@@ -208,31 +223,54 @@ func (s *IntegrationTestSuite) TestTWAP() {
 
 	// TWAP "from before to after swap" should be different from "from before to before swap"
 	// because swap introduces a new record with a different spot price.
-	s.T().Log("querying for the TWAP from before swap to now after swap, it must be less than the TWAPs before swap")
-	twapFromBeforeSwapToAfterSwap, err := chainANode.QueryArithmeticTwapToNow(poolId, denomOne, denomTwo, timeBeforeSwap)
+	s.T().Log("querying for the TWAP from before swap to now after swap")
+	twapFromBeforeSwapToAfterSwapAB, err := chainANode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap)
 	s.Require().NoError(err)
-	// We had a swap of 20000stake for some amount of uosmo.
-	// TWAP base is stake, quote is uosmo.
-	// After swap, we should be getting fewer uosmo for 1 stake.
-	s.Require().True(twapFromBeforeSwapToAfterSwap.LT(twapFromBeforeSwapToBeforeSwapOne))
-
-	s.T().Log("querying for the TWAP from after swap to now, must less than TWAPs before swap")
-	twapFromAfterToNow, err := chainANode.QueryArithmeticTwapToNow(poolId, denomOne, denomTwo, timeAfterSwap)
+	twapFromBeforeSwapToAfterSwapBC, err := chainANode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap)
 	s.Require().NoError(err)
-	// Because twapFromAfterToNow has a higher time weight for the after swap period between the two,
-	// we expect twapFromBeforeSwapToAfterSwap to be larger than twapFromAfterToNow
-	s.Require().True(twapFromBeforeSwapToAfterSwap.GT(twapFromAfterToNow))
-
-	s.T().Log("querying for the TWAP from after swap to after swap + 10ms, must be less than TWAPs before swap")
-	twapAfterSwapBeforePruning10Ms, err := chainANode.QueryArithmeticTwap(poolId, denomOne, denomTwo, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond))
+	twapFromBeforeSwapToAfterSwapCA, err := chainANode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap)
+	s.Require().NoError(err)
+	// We had a swap of 2000000stake for some amount of uion,
+	// 2000000uion for some amount of uosmo, and
+	// 2000000uosmo for some amount of stake
+	// Because we traded the same amount of all three assets, we expect the asset with the greatest
+	// initial value (B, or uion) to have a largest negative price impact,
+	// to the benefit (positive price impact) of the other two assets (A&C, or stake and uosmo)
+	s.Require().True(twapFromBeforeSwapToAfterSwapAB.GT(twapFromBeforeSwapToBeforeSwapOneAB))
+	s.Require().True(twapFromBeforeSwapToAfterSwapBC.LT(twapFromBeforeSwapToBeforeSwapOneBC))
+	s.Require().True(twapFromBeforeSwapToAfterSwapCA.GT(twapFromBeforeSwapToBeforeSwapOneCA))
+
+	s.T().Log("querying for the TWAP from after swap to now")
+	twapFromAfterToNowAB, err := chainANode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeAfterSwap)
+	s.Require().NoError(err)
+	twapFromAfterToNowBC, err := chainANode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeAfterSwap)
+	s.Require().NoError(err)
+	twapFromAfterToNowCA, err := chainANode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeAfterSwap)
+	s.Require().NoError(err)
+	// Because twapFromAfterToNow has a higher time weight for the after swap period,
+	// we expect the results to be flipped from the previous comparison to twapFromBeforeSwapToBeforeSwapOne
+	s.Require().True(twapFromBeforeSwapToAfterSwapAB.LT(twapFromAfterToNowAB))
+	s.Require().True(twapFromBeforeSwapToAfterSwapBC.GT(twapFromAfterToNowBC))
+	s.Require().True(twapFromBeforeSwapToAfterSwapCA.LT(twapFromAfterToNowCA))
+
+	s.T().Log("querying for the TWAP from after swap to after swap + 10ms")
+	twapAfterSwapBeforePruning10MsAB, err := chainANode.QueryArithmeticTwap(poolId, denomA, denomB, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond))
+	s.Require().NoError(err)
+	twapAfterSwapBeforePruning10MsBC, err := chainANode.QueryArithmeticTwap(poolId, denomB, denomC, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond))
+	s.Require().NoError(err)
+	twapAfterSwapBeforePruning10MsCA, err := chainANode.QueryArithmeticTwap(poolId, denomC, denomA, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond))
 	s.Require().NoError(err)
 	// Again, because twapAfterSwapBeforePruning10Ms has a higher time weight for the after swap period between the two,
-	// we expect twapFromBeforeSwapToAfterSwap to be larger than twapFromAfterToNow
-	s.Require().True(twapFromBeforeSwapToAfterSwap.GT(twapAfterSwapBeforePruning10Ms))
+	// we expect no change in the inequality
+	s.Require().True(twapFromBeforeSwapToAfterSwapAB.LT(twapAfterSwapBeforePruning10MsAB))
+	s.Require().True(twapFromBeforeSwapToAfterSwapBC.GT(twapAfterSwapBeforePruning10MsBC))
+	s.Require().True(twapFromBeforeSwapToAfterSwapCA.LT(twapAfterSwapBeforePruning10MsCA))
 
 	// These must be equal because they are calculated over time ranges with the stable and equal spot price.
 	// There are potential rounding errors requiring us to approximate the comparison.
-	osmoassert.DecApproxEq(s.T(), twapAfterSwapBeforePruning10Ms, twapFromAfterToNow, sdk.NewDecWithPrec(1, 3))
+	osmoassert.DecApproxEq(s.T(), twapAfterSwapBeforePruning10MsAB, twapFromAfterToNowAB, sdk.NewDecWithPrec(1, 3))
+	osmoassert.DecApproxEq(s.T(), twapAfterSwapBeforePruning10MsBC, twapFromAfterToNowBC, sdk.NewDecWithPrec(1, 3))
+	osmoassert.DecApproxEq(s.T(), twapAfterSwapBeforePruning10MsCA, twapFromAfterToNowCA, sdk.NewDecWithPrec(1, 3))
 
 	if !s.skipUpgrade {
 		// TODO: we should reduce the pruning time in the v11
@@ -254,23 +292,39 @@ func (s *IntegrationTestSuite) TestTWAP() {
 
 	// We should not be able to get TWAP before swap since it should have been pruned.
 	s.T().Log("pruning is now complete, querying TWAP for period that should be pruned")
-	_, err = chainANode.QueryArithmeticTwapToNow(poolId, denomOne, denomTwo, timeBeforeSwap)
+	_, err = chainANode.QueryArithmeticTwapToNow(poolId, denomA, denomB, timeBeforeSwap)
+	s.Require().ErrorContains(err, "too old")
+	_, err = chainANode.QueryArithmeticTwapToNow(poolId, denomB, denomC, timeBeforeSwap)
+	s.Require().ErrorContains(err, "too old")
+	_, err = chainANode.QueryArithmeticTwapToNow(poolId, denomC, denomA, timeBeforeSwap)
 	s.Require().ErrorContains(err, "too old")
 
 	// TWAPs for the same time range should be the same when we query for them before and after pruning.
 	s.T().Log("querying for TWAP for period before pruning took place but should not have been pruned")
-	twapAfterPruning10ms, err := chainANode.QueryArithmeticTwap(poolId, denomOne, denomTwo, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond))
+	twapAfterPruning10msAB, err := chainANode.QueryArithmeticTwap(poolId, denomA, denomB, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond))
 	s.Require().NoError(err)
-	s.Require().Equal(twapAfterSwapBeforePruning10Ms, twapAfterPruning10ms)
+	twapAfterPruning10msBC, err := chainANode.QueryArithmeticTwap(poolId, denomB, denomC, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond))
+	s.Require().NoError(err)
+	twapAfterPruning10msCA, err := chainANode.QueryArithmeticTwap(poolId, denomC, denomA, timeAfterSwap, timeAfterSwap.Add(10*time.Millisecond))
+	s.Require().NoError(err)
+	s.Require().Equal(twapAfterSwapBeforePruning10MsAB, twapAfterPruning10msAB)
+	s.Require().Equal(twapAfterSwapBeforePruning10MsBC, twapAfterPruning10msBC)
+	s.Require().Equal(twapAfterSwapBeforePruning10MsCA, twapAfterPruning10msCA)
 
 	// TWAP "from after to after swap" should equal to "from after swap to after pruning"
 	// These must be equal because they are calculated over time ranges with the stable and equal spot price.
 	timeAfterPruning := chainANode.QueryLatestBlockTime()
 	s.T().Log("querying for TWAP from after swap to after pruning")
-	twapToNowPostPruning, err := chainANode.QueryArithmeticTwap(poolId, denomOne, denomTwo, timeAfterSwap, timeAfterPruning)
+	twapToNowPostPruningAB, err := chainANode.QueryArithmeticTwap(poolId, denomA, denomB, timeAfterSwap, timeAfterPruning)
+	s.Require().NoError(err)
+	twapToNowPostPruningBC, err := chainANode.QueryArithmeticTwap(poolId, denomB, denomC, timeAfterSwap, timeAfterPruning)
+	s.Require().NoError(err)
+	twapToNowPostPruningCA, err := chainANode.QueryArithmeticTwap(poolId, denomC, denomA, timeAfterSwap, timeAfterPruning)
 	s.Require().NoError(err)
 	// There are potential rounding errors requiring us to approximate the comparison.
-	osmoassert.DecApproxEq(s.T(), twapToNowPostPruning, twapAfterSwapBeforePruning10Ms, sdk.NewDecWithPrec(1, 3))
+	osmoassert.DecApproxEq(s.T(), twapToNowPostPruningAB, twapAfterSwapBeforePruning10MsAB, sdk.NewDecWithPrec(1, 3))
+	osmoassert.DecApproxEq(s.T(), twapToNowPostPruningBC, twapAfterSwapBeforePruning10MsBC, sdk.NewDecWithPrec(1, 3))
+	osmoassert.DecApproxEq(s.T(), twapToNowPostPruningCA, twapAfterSwapBeforePruning10MsCA, sdk.NewDecWithPrec(1, 3))
 }
 
 func (s *IntegrationTestSuite) TestStateSync() {
diff --git a/tests/e2e/initialization/config.go b/tests/e2e/initialization/config.go
index d3adc1a52..adb86b44c 100644
--- a/tests/e2e/initialization/config.go
+++ b/tests/e2e/initialization/config.go
@@ -46,6 +46,7 @@ type NodeConfig struct {
 const (
 	// common
 	OsmoDenom           = "uosmo"
+	IonDenom            = "uion"
 	StakeDenom          = "stake"
 	OsmoIBCDenom        = "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518"
 	StakeIBCDenom       = "ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B7787"
@@ -55,11 +56,13 @@ const (
 	// chainA
 	ChainAID      = "osmo-test-a"
 	OsmoBalanceA  = 200000000000
+	IonBalanceA   = 100000000000
 	StakeBalanceA = 110000000000
 	StakeAmountA  = 100000000000
 	// chainB
 	ChainBID      = "osmo-test-b"
 	OsmoBalanceB  = 500000000000
+	IonBalanceB   = 100000000000
 	StakeBalanceB = 440000000000
 	StakeAmountB  = 400000000000
 
@@ -73,8 +76,8 @@ var (
 	StakeAmountIntB  = sdk.NewInt(StakeAmountB)
 	StakeAmountCoinB = sdk.NewCoin(OsmoDenom, StakeAmountIntB)
 
-	InitBalanceStrA = fmt.Sprintf("%d%s,%d%s", OsmoBalanceA, OsmoDenom, StakeBalanceA, StakeDenom)
-	InitBalanceStrB = fmt.Sprintf("%d%s,%d%s", OsmoBalanceB, OsmoDenom, StakeBalanceB, StakeDenom)
+	InitBalanceStrA = fmt.Sprintf("%d%s,%d%s,%d%s", OsmoBalanceA, OsmoDenom, StakeBalanceA, StakeDenom, IonBalanceA, IonDenom)
+	InitBalanceStrB = fmt.Sprintf("%d%s,%d%s,%d%s", OsmoBalanceB, OsmoDenom, StakeBalanceB, StakeDenom, IonBalanceB, IonDenom)
 	OsmoToken       = sdk.NewInt64Coin(OsmoDenom, IbcSendAmount)  // 3,300uosmo
 	StakeToken      = sdk.NewInt64Coin(StakeDenom, IbcSendAmount) // 3,300ustake
 	tenOsmo         = sdk.Coins{sdk.NewInt64Coin(OsmoDenom, 10_000_000)}
diff --git a/tests/e2e/scripts/nativeDenomThreeAssetPool.json b/tests/e2e/scripts/nativeDenomThreeAssetPool.json
new file mode 100644
index 000000000..e86d42b90
--- /dev/null
+++ b/tests/e2e/scripts/nativeDenomThreeAssetPool.json
@@ -0,0 +1,7 @@
+{
+    "weights": "5stake,5uion,5uosmo",
+    "initial-deposit": "10000000stake,1000000uion,20000000uosmo",
+    "swap-fee": "0.01",
+    "exit-fee": "0.01",
+    "future-governor": ""
+}
diff --git a/tests/localosmosis/scripts/add_keys.sh b/tests/localosmosis/scripts/add_keys.sh
index 084337ee4..14c430394 100755
--- a/tests/localosmosis/scripts/add_keys.sh
+++ b/tests/localosmosis/scripts/add_keys.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-echo "satisfy adjust timber high purchase tuition stool faith fine install that you unaware feed domain license impose boss human eager hat rent enjoy dawn" | osmosisd keys add lo-val --recover --keyring-backend test
+echo "bottom loan skill merry east cradle onion journey palm apology verb edit desert impose absurd oil bubble sweet glove shallow size build burst effort" | osmosisd keys add val --recover --keyring-backend test
 echo "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius" | osmosisd keys add lo-test1 --recover --keyring-backend test
 echo "quality vacuum heart guard buzz spike sight swarm shove special gym robust assume sudden deposit grid alcohol choice devote leader tilt noodle tide penalty" | osmosisd keys add lo-test2 --recover --keyring-backend test
 echo "symbol force gallery make bulk round subway violin worry mixture penalty kingdom boring survey tool fringe patrol sausage hard admit remember broken alien absorb" | osmosisd keys add lo-test3 --recover --keyring-backend test
diff --git a/tests/localosmosis/scripts/setup.sh b/tests/localosmosis/scripts/setup.sh
index 8410d4bfd..9cad1a003 100755
--- a/tests/localosmosis/scripts/setup.sh
+++ b/tests/localosmosis/scripts/setup.sh
@@ -5,7 +5,7 @@ OSMOSIS_HOME=$HOME/.osmosisd
 CONFIG_FOLDER=$OSMOSIS_HOME/config
 MONIKER=val
 
-MNEMONIC="satisfy adjust timber high purchase tuition stool faith fine install that you unaware feed domain license impose boss human eager hat rent enjoy dawn"
+MNEMONIC="bottom loan skill merry east cradle onion journey palm apology verb edit desert impose absurd oil bubble sweet glove shallow size build burst effort"
 
 install_prerequisites () {
     apk add dasel
@@ -40,7 +40,7 @@ edit_genesis () {
     dasel put string -f $GENESIS '.app_state.incentives.lockable_durations.[1]' "120s"
     dasel put string -f $GENESIS '.app_state.incentives.lockable_durations.[2]' "180s"
     dasel put string -f $GENESIS '.app_state.incentives.lockable_durations.[3]' "240s"
-    dasel put string -f $GENESIS '.app_state.incentives.params.distr_epoch_identifier' "day" 
+    dasel put string -f $GENESIS '.app_state.incentives.params.distr_epoch_identifier' "day"
 
     # Update mint module
     dasel put string -f $GENESIS '.app_state.mint.params.mint_denom' "uosmo"
@@ -51,11 +51,14 @@ edit_genesis () {
 
     # Update txfee basedenom
     dasel put string -f $GENESIS '.app_state.txfees.basedenom' "uosmo"
+
+    # Update wasm permission (Nobody or Everybody)
+    dasel put string -f $GENESIS '.app_state.wasm.params.code_upload_access.permission' "Nobody"
 }
 
 add_genesis_accounts () {
 
-    osmosisd add-genesis-account osmo1phaxpevm5wecex2jyaqty2a4v02qj7qmlmzk5a 100000000000uosmo,100000000000uion --home $OSMOSIS_HOME 
+    osmosisd add-genesis-account osmo12smx2wdlyttvyzvzg54y2vnqwq2qjateuf7thj 100000000000uosmo,100000000000uion --home $OSMOSIS_HOME 
     osmosisd add-genesis-account osmo1cyyzpxplxdzkeea7kwsydadg87357qnahakaks 100000000000uosmo,100000000000uion --home $OSMOSIS_HOME
     osmosisd add-genesis-account osmo18s5lynnmx37hq4wlrw9gdn68sg2uxp5rgk26vv 100000000000uosmo,100000000000uion --home $OSMOSIS_HOME
     osmosisd add-genesis-account osmo1qwexv7c6sm95lwhzn9027vyu2ccneaqad4w8ka 100000000000uosmo,100000000000uion --home $OSMOSIS_HOME
diff --git a/tests/simulator/state.go b/tests/simulator/state.go
index 3643cf556..3d94028ec 100644
--- a/tests/simulator/state.go
+++ b/tests/simulator/state.go
@@ -29,7 +29,10 @@ func AppStateFn() osmosim.AppStateFn {
 	return func(simManager *osmosimtypes.Manager, r *rand.Rand, accs []simtypes.Account, config osmosim.InitializationConfig,
 	) (appState json.RawMessage, simAccs []simtypes.Account, chainID string, genesisTimestamp time.Time) {
 		if osmosim.FlagGenesisTimeValue == 0 {
-			genesisTimestamp = simtypes.RandTimestamp(r)
+			// N.B.: wasmd has the following check in its simulator:
+			// https://github.com/osmosis-labs/wasmd/blob/c2ec9092d086b5ac6dd367f33ce8b5cce8e4c5f5/x/wasm/types/types.go#L261-L264
+			// As a result, it is easy to overflow and become negative if seconds are set too large.
+			genesisTimestamp = time.Unix(0, r.Int63())
 		} else {
 			genesisTimestamp = time.Unix(osmosim.FlagGenesisTimeValue, 0)
 		}
diff --git a/wasmbinding/query_plugin_test.go b/wasmbinding/query_plugin_test.go
index 2cf928129..0d40b7f42 100644
--- a/wasmbinding/query_plugin_test.go
+++ b/wasmbinding/query_plugin_test.go
@@ -52,7 +52,6 @@ func (suite *StargateTestSuite) TestStargateQuerier() {
 		expectedUnMarshalError bool
 		resendRequest          bool
 	}{
-
 		{
 			name: "happy path",
 			path: "/osmosis.epochs.v1beta1.Query/EpochInfos",
@@ -280,22 +279,18 @@ func (suite *StargateTestSuite) TestDeterministicJsonMarshal() {
 	testCases := []struct {
 		name                string
 		testSetup           func()
-		originalResponse    string
-		updatedResponse     string
+		originalResponsebz  []byte
+		updatedResponsebz   []byte
 		queryPath           string
 		responseProtoStruct interface{}
 		expectedProto       func() proto.Message
 	}{
-
 		/**
 		   * Origin Response
 		   * balances:<denom:"bar" amount:"30" > pagination:<next_key:"foo" >
-		   * "0a090a036261721202333012050a03666f6f"
-		   *
 		   * New Version Response
 		   * The binary built from the proto response with additional field address
 		   * balances:<denom:"bar" amount:"30" > pagination:<next_key:"foo" > address:"cosmos1j6j5tsquq2jlw2af7l3xekyaq7zg4l8jsufu78"
-		   * "0a090a036261721202333012050a03666f6f1a2d636f736d6f73316a366a357473717571326a6c77326166376c3378656b796171377a67346c386a737566753738"
 		   // Origin proto
 		   message QueryAllBalancesResponse {
 		  	// balances is the balances of all the coins.
@@ -320,8 +315,12 @@ func (suite *StargateTestSuite) TestDeterministicJsonMarshal() {
 			func() {
 				wasmbinding.SetWhitelistedQuery("/cosmos.bank.v1beta1.Query/AllBalances", &banktypes.QueryAllBalancesResponse{})
 			},
-			"0a090a036261721202333012050a03666f6f",
-			"0a090a036261721202333012050a03666f6f1a2d636f736d6f73316a366a357473717571326a6c77326166376c3378656b796171377a67346c386a737566753738",
+			[]byte{10, 9, 10, 3, 98, 97, 114, 18, 2, 51, 48, 18, 5, 10, 3, 102, 111, 111},
+			[]byte{
+				10, 9, 10, 3, 98, 97, 114, 18, 2, 51, 48, 18, 5, 10, 3, 102, 111, 111, 26, 45, 99, 111, 115, 109, 111, 115, 49, 106,
+				54, 106, 53, 116, 115, 113, 117, 113, 50, 106, 108, 119, 50, 97, 102, 55, 108, 51, 120, 101, 107, 121, 97, 113, 55, 122, 103,
+				52, 108, 56, 106, 115, 117, 102, 117, 55, 56,
+			},
 			"/cosmos.bank.v1beta1.Query/AllBalances",
 			&banktypes.QueryAllBalancesResponse{},
 			func() proto.Message {
@@ -334,12 +333,6 @@ func (suite *StargateTestSuite) TestDeterministicJsonMarshal() {
 			},
 		},
 		/**
-		   *
-		   * Origin Response
-		   * 0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f7331346c3268686a6e676c3939367772703935673867646a6871653038326375367a7732706c686b
-		   *
-		   * Updated Response
-		   * 0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f7331646a783375676866736d6b6135386676673076616a6e6533766c72776b7a6a346e6377747271122d636f736d6f7331646a783375676866736d6b6135386676673076616a6e6533766c72776b7a6a346e6377747271
 		  // Origin proto
 		  message QueryAccountResponse {
 		    // account defines the account of the corresponding address.
@@ -356,8 +349,18 @@ func (suite *StargateTestSuite) TestDeterministicJsonMarshal() {
 		{
 			"Query Account",
 			nil,
-			"0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679",
-			"0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679122d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679",
+			[]byte{
+				10, 83, 10, 32, 47, 99, 111, 115, 109, 111, 115, 46, 97, 117, 116, 104, 46, 118, 49, 98, 101, 116, 97, 49, 46, 66, 97, 115,
+				101, 65, 99, 99, 111, 117, 110, 116, 18, 47, 10, 45, 99, 111, 115, 109, 111, 115, 49, 102, 56, 117, 120, 117, 108, 116, 110, 56,
+				115, 113, 122, 104, 122, 110, 114, 115, 122, 51, 113, 55, 55, 120, 119, 97, 113, 117, 104, 103, 114, 115, 103, 54, 106, 121, 118, 102, 121,
+			},
+			[]byte{
+				10, 83, 10, 32, 47, 99, 111, 115, 109, 111, 115, 46, 97, 117, 116, 104, 46, 118, 49, 98, 101, 116, 97, 49, 46, 66, 97, 115,
+				101, 65, 99, 99, 111, 117, 110, 116, 18, 47, 10, 45, 99, 111, 115, 109, 111, 115, 49, 102, 56, 117, 120, 117, 108, 116, 110, 56,
+				115, 113, 122, 104, 122, 110, 114, 115, 122, 51, 113, 55, 55, 120, 119, 97, 113, 117, 104, 103, 114, 115, 103, 54, 106, 121, 118, 102, 121,
+				18, 45, 99, 111, 115, 109, 111, 115, 49, 102, 56, 117, 120, 117, 108, 116, 110, 56, 115, 113, 122, 104, 122, 110, 114, 115, 122, 51, 113, 55,
+				55, 120, 119, 97, 113, 117, 104, 103, 114, 115, 103, 54, 106, 121, 118, 102, 121,
+			},
 			"/cosmos.auth.v1beta1.Query/Account",
 			&authtypes.QueryAccountResponse{},
 			func() proto.Message {
@@ -384,14 +387,11 @@ func (suite *StargateTestSuite) TestDeterministicJsonMarshal() {
 			binding, err := wasmbinding.GetWhitelistedQuery(tc.queryPath)
 			suite.Require().Nil(err)
 
-			originVersionBz, err := hex.DecodeString(tc.originalResponse)
 			suite.Require().NoError(err)
-			jsonMarshalledOriginalBz, err := wasmbinding.ConvertProtoToJSONMarshal(binding, originVersionBz, suite.app.AppCodec())
+			jsonMarshalledOriginalBz, err := wasmbinding.ConvertProtoToJSONMarshal(binding, tc.originalResponsebz, suite.app.AppCodec())
 			suite.Require().NoError(err)
 
-			newVersionBz, err := hex.DecodeString(tc.updatedResponse)
-			suite.Require().NoError(err)
-			jsonMarshalledUpdatedBz, err := wasmbinding.ConvertProtoToJSONMarshal(binding, newVersionBz, suite.app.AppCodec())
+			jsonMarshalledUpdatedBz, err := wasmbinding.ConvertProtoToJSONMarshal(binding, tc.updatedResponsebz, suite.app.AppCodec())
 			suite.Require().NoError(err)
 
 			// json marshalled bytes should be the same since we use the same proto struct for unmarshalling
diff --git a/x/epochs/types/hooks_test.go b/x/epochs/types/hooks_test.go
index 06e00f053..419e77229 100644
--- a/x/epochs/types/hooks_test.go
+++ b/x/epochs/types/hooks_test.go
@@ -77,7 +77,6 @@ func (hook *dummyEpochHook) BeforeEpochStart(ctx sdk.Context, epochIdentifier st
 	hook.successCounter += 1
 	ctx.EventManager().EmitEvent(dummyBeforeEpochStartEvent(epochIdentifier, epochNumber))
 	return nil
-
 }
 
 func (hook *dummyEpochHook) Clone() *dummyEpochHook {
@@ -91,7 +90,7 @@ func (suite *KeeperTestSuite) TestHooksPanicRecovery() {
 	panicHook := dummyEpochHook{shouldPanic: true}
 	noPanicHook := dummyEpochHook{shouldPanic: false}
 	errorHook := dummyEpochHook{shouldError: true}
-	noErrorHook := dummyEpochHook{shouldError: false} //same as nopanic
+	noErrorHook := dummyEpochHook{shouldError: false} // same as nopanic
 	simpleHooks := []dummyEpochHook{panicHook, noPanicHook, errorHook, noErrorHook}
 
 	tests := []struct {
diff --git a/x/gamm/keeper/multihop_test.go b/x/gamm/keeper/multihop_test.go
index 73f08d647..89ffc29ce 100644
--- a/x/gamm/keeper/multihop_test.go
+++ b/x/gamm/keeper/multihop_test.go
@@ -2,6 +2,7 @@ package keeper_test
 
 import (
 	"errors"
+
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/osmosis-labs/osmosis/v12/x/gamm/pool-models/balancer"
 
diff --git a/x/gamm/keeper/swap_test.go b/x/gamm/keeper/swap_test.go
index fd0cd8497..b924d098c 100644
--- a/x/gamm/keeper/swap_test.go
+++ b/x/gamm/keeper/swap_test.go
@@ -324,5 +324,4 @@ func (suite *KeeperTestSuite) TestInactivePoolFreezeSwaps() {
 			}
 		})
 	}
-
 }
diff --git a/x/gamm/pool-models/balancer/amm_test.go b/x/gamm/pool-models/balancer/amm_test.go
index 68e5daa59..c771693eb 100644
--- a/x/gamm/pool-models/balancer/amm_test.go
+++ b/x/gamm/pool-models/balancer/amm_test.go
@@ -8,7 +8,7 @@ import (
 
 	"github.com/osmosis-labs/osmosis/v12/x/gamm/pool-models/balancer"
 	"github.com/osmosis-labs/osmosis/v12/x/gamm/pool-models/internal/test_helpers"
-  "github.com/osmosis-labs/osmosis/v12/x/gamm/types"
+	"github.com/osmosis-labs/osmosis/v12/x/gamm/types"
 )
 
 type BalancerTestSuite struct {
diff --git a/x/gamm/pool-models/balancer/pool_suite_test.go b/x/gamm/pool-models/balancer/pool_suite_test.go
index b425aaab6..aa25cac11 100644
--- a/x/gamm/pool-models/balancer/pool_suite_test.go
+++ b/x/gamm/pool-models/balancer/pool_suite_test.go
@@ -50,7 +50,8 @@ var (
 //
 // CalcJoinPoolShares calls calcSingleAssetJoin. As a result, we can reuse
 // the same test cases for unit testing both calcSingleAssetJoin and
-//  CalcJoinPoolShares with only one tokensIn.
+//
+//	CalcJoinPoolShares with only one tokensIn.
 type calcJoinSharesTestCase struct {
 	name         string
 	swapFee      sdk.Dec
@@ -898,7 +899,7 @@ func (suite *KeeperTestSuite) TestJoinPoolNoSwap() {
 				sdk.NewInt64Coin("uosmo", 25_000),
 				sdk.NewInt64Coin("uatom", 50_000),
 			),
-	
+
 			expectShares: sdk.NewInt(2.5e12),
 			expectLiq: sdk.NewCoins(
 				sdk.NewInt64Coin("uosmo", 25_000),
@@ -914,7 +915,7 @@ func (suite *KeeperTestSuite) TestJoinPoolNoSwap() {
 				sdk.NewInt64Coin("uosmo", 25_000),
 				sdk.NewInt64Coin("uatom", 50_000),
 			),
-	
+
 			expectShares: sdk.NewInt(2.5e12),
 			expectLiq: sdk.NewCoins(
 				sdk.NewInt64Coin("uosmo", 25_000),
diff --git a/x/gamm/pool-models/balancer/pool_test.go b/x/gamm/pool-models/balancer/pool_test.go
index eac0313c0..d7a5ef60b 100644
--- a/x/gamm/pool-models/balancer/pool_test.go
+++ b/x/gamm/pool-models/balancer/pool_test.go
@@ -1329,12 +1329,12 @@ func TestCalcJoinPoolNoSwapShares(t *testing.T) {
 			expectPass: false,
 		},
 		"attempt joining pool with no assets in it": {
-			tokensIn: sdk.Coins{sdk.NewCoin("foo", sdk.NewInt(1)), sdk.NewCoin("foo", sdk.NewInt(1))},
-			poolAssets: []balancer.PoolAsset{},
+			tokensIn:        sdk.Coins{sdk.NewCoin("foo", sdk.NewInt(1)), sdk.NewCoin("foo", sdk.NewInt(1))},
+			poolAssets:      []balancer.PoolAsset{},
 			expNumShare:     sdk.NewIntFromUint64(0),
 			expTokensJoined: sdk.Coins{},
-			expPoolAssets: []balancer.PoolAsset{},
-			expectPass: false,
+			expPoolAssets:   []balancer.PoolAsset{},
+			expectPass:      false,
 		},
 	}
 
diff --git a/x/gamm/pool-models/stableswap/amm_bench_test.go b/x/gamm/pool-models/stableswap/amm_bench_test.go
index a23546376..a868b8f86 100644
--- a/x/gamm/pool-models/stableswap/amm_bench_test.go
+++ b/x/gamm/pool-models/stableswap/amm_bench_test.go
@@ -4,7 +4,7 @@ import (
 	"math/rand"
 	"testing"
 
-	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/osmosis-labs/osmosis/v12/osmomath"
 )
 
 func BenchmarkCFMM(b *testing.B) {
@@ -20,9 +20,9 @@ func BenchmarkBinarySearch(b *testing.B) {
 	}
 }
 
-func runCalc(solve func(sdk.Dec, sdk.Dec, sdk.Dec) sdk.Dec) {
-	xReserve := sdk.NewDec(rand.Int63n(100000) + 50000)
-	yReserve := sdk.NewDec(rand.Int63n(100000) + 50000)
-	yIn := sdk.NewDec(rand.Int63n(100000))
+func runCalc(solve func(osmomath.BigDec, osmomath.BigDec, osmomath.BigDec) osmomath.BigDec) {
+	xReserve := osmomath.NewBigDec(rand.Int63n(100000) + 50000)
+	yReserve := osmomath.NewBigDec(rand.Int63n(100000) + 50000)
+	yIn := osmomath.NewBigDec(rand.Int63n(100000))
 	solve(xReserve, yReserve, yIn)
 }
diff --git a/x/gamm/pool-models/stableswap/amm_test.go b/x/gamm/pool-models/stableswap/amm_test.go
index df70b1e44..e2c67eadf 100644
--- a/x/gamm/pool-models/stableswap/amm_test.go
+++ b/x/gamm/pool-models/stableswap/amm_test.go
@@ -1,13 +1,14 @@
 package stableswap
 
 import (
-	fmt "fmt"
+	"fmt"
 	"testing"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/stretchr/testify/require"
 
 	"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/test_helpers"
 )
 
@@ -16,87 +17,174 @@ type StableSwapTestSuite struct {
 }
 
 func TestCFMMInvariantTwoAssets(t *testing.T) {
-	kErrTolerance := sdk.OneDec()
+	kErrTolerance := osmomath.OneDec()
 
-	tests := []struct {
-		xReserve sdk.Dec
-		yReserve sdk.Dec
-		yIn      sdk.Dec
+	tests := map[string]struct {
+		xReserve    osmomath.BigDec
+		yReserve    osmomath.BigDec
+		yIn         osmomath.BigDec
+		expectPanic bool
 	}{
-		{
-			sdk.NewDec(100),
-			sdk.NewDec(100),
-			sdk.NewDec(1),
+		"small pool small input": {
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(1),
+			false,
 		},
-		{
-			sdk.NewDec(100),
-			sdk.NewDec(100),
-			sdk.NewDec(1000),
+		"small pool large input": {
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(1000),
+			false,
 		},
+		// This test fails due to a bug in our original solver
 		// {
 		// 	sdk.NewDec(100000),
 		// 	sdk.NewDec(100000),
 		// 	sdk.NewDec(10000),
 		// },
+
+		// panic catching
+		"xReserve negative": {
+			osmomath.NewBigDec(-100),
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(1),
+			true,
+		},
+		"yReserve negative": {
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(-100),
+			osmomath.NewBigDec(1),
+			true,
+		},
+		"yIn negative": {
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(-1),
+			true,
+		},
 	}
 
-	for _, test := range tests {
-		// using two-asset cfmm
-		k0 := cfmmConstant(test.xReserve, test.yReserve)
-		xOut := solveCfmm(test.xReserve, test.yReserve, test.yIn)
-
-		k1 := cfmmConstant(test.xReserve.Sub(xOut), test.yReserve.Add(test.yIn))
-		osmoassert.DecApproxEq(t, k0, k1, kErrTolerance)
-
-		// using multi-asset cfmm (should be equivalent with u = 1, w = 0)
-		k2 := cfmmConstantMulti(test.xReserve, test.yReserve, sdk.OneDec(), sdk.ZeroDec())
-		osmoassert.DecApproxEq(t, k2, k0, kErrTolerance)
-		xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, sdk.ZeroDec(), test.yIn)
-		fmt.Println(xOut2)
-		k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), sdk.OneDec(), sdk.ZeroDec())
-		osmoassert.DecApproxEq(t, k2, k3, kErrTolerance)
+	for name, test := range tests {
+		t.Run(name, func(t *testing.T) {
+			// system under test
+			sut := func() {
+				// using two-asset cfmm
+				k0 := cfmmConstant(test.xReserve, test.yReserve)
+				xOut := solveCfmm(test.xReserve, test.yReserve, test.yIn)
+
+				k1 := cfmmConstant(test.xReserve.Sub(xOut), test.yReserve.Add(test.yIn))
+				osmomath.DecApproxEq(t, k0, k1, kErrTolerance)
+
+				// using multi-asset cfmm (should be equivalent with u = 1, w = 0)
+				k2 := cfmmConstantMulti(test.xReserve, test.yReserve, osmomath.OneDec(), osmomath.ZeroDec())
+				osmomath.DecApproxEq(t, k2, k0, kErrTolerance)
+				xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, osmomath.ZeroDec(), test.yIn)
+				k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), osmomath.OneDec(), osmomath.ZeroDec())
+				osmomath.DecApproxEq(t, k2, k3, kErrTolerance)
+			}
+
+			osmoassert.ConditionalPanic(t, test.expectPanic, sut)
+		})
 	}
 }
 
 func TestCFMMInvariantMultiAssets(t *testing.T) {
-	kErrTolerance := sdk.OneDec()
-
-	tests := []struct {
-		xReserve    sdk.Dec
-		yReserve    sdk.Dec
-		uReserve    sdk.Dec
-		wSumSquares sdk.Dec
-		yIn         sdk.Dec
+	kErrTolerance := osmomath.OneDec()
+
+	tests := map[string]struct {
+		xReserve    osmomath.BigDec
+		yReserve    osmomath.BigDec
+		uReserve    osmomath.BigDec
+		wSumSquares osmomath.BigDec
+		yIn         osmomath.BigDec
+		expectPanic bool
 	}{
-		{
-			sdk.NewDec(100),
-			sdk.NewDec(100),
+		"4-asset pool, small input": {
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(100),
 			// represents a 4-asset pool with 100 in each reserve
-			sdk.NewDec(200),
-			sdk.NewDec(20000),
-			sdk.NewDec(1),
+			osmomath.NewBigDec(200),
+			osmomath.NewBigDec(20000),
+			osmomath.NewBigDec(1),
+			false,
 		},
-		{
-			sdk.NewDec(100),
-			sdk.NewDec(100),
-			sdk.NewDec(200),
-			sdk.NewDec(20000),
-			sdk.NewDec(1000),
+		"4-asset pool, large input": {
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(200),
+			osmomath.NewBigDec(20000),
+			osmomath.NewBigDec(1000),
+			false,
 		},
-		// {
+		// This test fails due to a bug in our original solver
+		// "large pool, large input": {
 		// 	sdk.NewDec(100000),
 		// 	sdk.NewDec(100000),
 		// 	sdk.NewDec(10000),
 		// },
+
+		// panic catching
+		"negative xReserve": {
+			osmomath.NewBigDec(-100),
+			osmomath.NewBigDec(100),
+			// represents a 4-asset pool with 100 in each reserve
+			osmomath.NewBigDec(200),
+			osmomath.NewBigDec(20000),
+			osmomath.NewBigDec(1),
+			true,
+		},
+		"negative yReserve": {
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(-100),
+			// represents a 4-asset pool with 100 in each reserve
+			osmomath.NewBigDec(200),
+			osmomath.NewBigDec(20000),
+			osmomath.NewBigDec(1),
+			true,
+		},
+		"negative uReserve": {
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(100),
+			// represents a 4-asset pool with 100 in each reserve
+			osmomath.NewBigDec(-200),
+			osmomath.NewBigDec(20000),
+			osmomath.NewBigDec(1),
+			true,
+		},
+		"negative sumSquares": {
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(100),
+			// represents a 4-asset pool with 100 in each reserve
+			osmomath.NewBigDec(200),
+			osmomath.NewBigDec(-20000),
+			osmomath.NewBigDec(1),
+			true,
+		},
+		"negative yIn": {
+			osmomath.NewBigDec(100),
+			osmomath.NewBigDec(100),
+			// represents a 4-asset pool with 100 in each reserve
+			osmomath.NewBigDec(200),
+			osmomath.NewBigDec(-20000),
+			osmomath.NewBigDec(1),
+			true,
+		},
 	}
 
-	for _, test := range tests {
-		// using multi-asset cfmm
-		k2 := cfmmConstantMulti(test.xReserve, test.yReserve, test.uReserve, test.wSumSquares)
-		xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, test.wSumSquares, test.yIn)
-		fmt.Println(xOut2)
-		k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), test.uReserve, test.wSumSquares)
-		osmoassert.DecApproxEq(t, k2, k3, kErrTolerance)
+	for name, test := range tests {
+		t.Run(name, func(t *testing.T) {
+			// system under test
+			sut := func() {
+				// using multi-asset cfmm
+				k2 := cfmmConstantMulti(test.xReserve, test.yReserve, test.uReserve, test.wSumSquares)
+				xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, test.wSumSquares, test.yIn)
+				k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), test.uReserve, test.wSumSquares)
+				osmomath.DecApproxEq(t, k2, k3, kErrTolerance)
+			}
+
+			osmoassert.ConditionalPanic(t, test.expectPanic, sut)
+		})
 	}
 }
 
diff --git a/x/ibc-rate-limit/types/params_test.go b/x/ibc-rate-limit/types/params_test.go
index ea2a2d67a..657c6422b 100644
--- a/x/ibc-rate-limit/types/params_test.go
+++ b/x/ibc-rate-limit/types/params_test.go
@@ -1,8 +1,9 @@
 package types
 
 import (
-	"github.com/stretchr/testify/require"
 	"testing"
+
+	"github.com/stretchr/testify/require"
 )
 
 func TestValidateContractAddress(t *testing.T) {
diff --git a/x/incentives/keeper/msg_server_test.go b/x/incentives/keeper/msg_server_test.go
index 8526e0585..d3793767d 100644
--- a/x/incentives/keeper/msg_server_test.go
+++ b/x/incentives/keeper/msg_server_test.go
@@ -134,7 +134,6 @@ func (suite *KeeperTestSuite) TestCreateGauge_Fee() {
 }
 
 func (suite *KeeperTestSuite) TestAddToGauge_Fee() {
-
 	tests := []struct {
 		name                 string
 		accountBalanceToFund sdk.Coins
diff --git a/x/lockup/types/msgs_test.go b/x/lockup/types/msgs_test.go
index 29a1fc807..6d0ab74ed 100644
--- a/x/lockup/types/msgs_test.go
+++ b/x/lockup/types/msgs_test.go
@@ -167,12 +167,22 @@ func TestMsgBeginUnlocking(t *testing.T) {
 			},
 		},
 		{
-			name: "not positive coins amount",
+			name: "zero coins (same as nil)",
 			msg: types.MsgBeginUnlocking{
 				Owner: addr1,
 				ID:    1,
 				Coins: sdk.NewCoins(sdk.NewCoin("test1", sdk.NewInt(0))),
 			},
+			expectPass: true,
+		},
+		{
+			name: "nil coins (unlock by ID)",
+			msg: types.MsgBeginUnlocking{
+				Owner: addr1,
+				ID:    1,
+				Coins: sdk.NewCoins(),
+			},
+			expectPass: true,
 		},
 	}
 
diff --git a/x/mint/keeper/export_test.go b/x/mint/keeper/export_test.go
index 10d4af137..8af33b19e 100644
--- a/x/mint/keeper/export_test.go
+++ b/x/mint/keeper/export_test.go
@@ -16,9 +16,7 @@ const (
 	DeveloperVestingAmount       = developerVestingAmount
 )
 
-var (
-	GetProportions = getProportions
-)
+var GetProportions = getProportions
 
 func (k Keeper) CreateDeveloperVestingModuleAccount(ctx sdk.Context, amount sdk.Coin) error {
 	return k.createDeveloperVestingModuleAccount(ctx, amount)
diff --git a/x/mint/keeper/keeper_test.go b/x/mint/keeper/keeper_test.go
index dc3c1b7b6..f076d4395 100644
--- a/x/mint/keeper/keeper_test.go
+++ b/x/mint/keeper/keeper_test.go
@@ -158,9 +158,7 @@ func (suite *KeeperTestSuite) TestDistributeMintedCoin() {
 		mintAmount = 10000
 	)
 
-	var (
-		params = types.DefaultParams()
-	)
+	params := types.DefaultParams()
 
 	tests := []struct {
 		name              string
diff --git a/x/pool-incentives/keeper/genesis_test.go b/x/pool-incentives/keeper/genesis_test.go
index 4ccc6a643..37ba26504 100644
--- a/x/pool-incentives/keeper/genesis_test.go
+++ b/x/pool-incentives/keeper/genesis_test.go
@@ -79,25 +79,38 @@ func TestInitGenesis(t *testing.T) {
 	require.Equal(t, distrInfo, *genesis.DistrInfo)
 }
 
-func TestExportGenesis(t *testing.T) {
-	app := simapp.Setup(false)
-	ctx := app.BaseApp.NewContext(false, tmproto.Header{})
+func (suite *KeeperTestSuite) TestExportGenesis() {
+	ctx := suite.App.BaseApp.NewContext(false, tmproto.Header{})
 	ctx = ctx.WithBlockTime(now.Add(time.Second))
 	genesis := testGenesis
-	app.PoolIncentivesKeeper.InitGenesis(ctx, &genesis)
+	suite.App.PoolIncentivesKeeper.InitGenesis(ctx, &genesis)
+
+	lockableDurations := suite.App.PoolIncentivesKeeper.GetLockableDurations(ctx)
+	suite.App.IncentivesKeeper.SetLockableDurations(ctx, lockableDurations)
+	poolId := suite.PrepareBalancerPool()
 
 	durations := []time.Duration{
 		time.Second,
 		time.Minute,
 		time.Hour,
-		time.Hour * 5,
 	}
-	app.PoolIncentivesKeeper.SetLockableDurations(ctx, durations)
-	savedDurations := app.PoolIncentivesKeeper.GetLockableDurations(ctx)
-	require.Equal(t, savedDurations, durations)
-
-	genesisExported := app.PoolIncentivesKeeper.ExportGenesis(ctx)
-	require.Equal(t, genesisExported.Params, genesis.Params)
-	require.Equal(t, genesisExported.LockableDurations, durations)
-	require.Equal(t, genesisExported.DistrInfo, genesis.DistrInfo)
+	suite.App.PoolIncentivesKeeper.SetLockableDurations(ctx, durations)
+	savedDurations := suite.App.PoolIncentivesKeeper.GetLockableDurations(ctx)
+	suite.Equal(savedDurations, durations)
+	var expectedPoolToGauges types.PoolToGauges
+	var gauge uint64
+	for _, duration := range durations {
+		gauge++
+		var poolToGauge types.PoolToGauge
+		poolToGauge.Duration = duration
+		poolToGauge.PoolId = poolId
+		poolToGauge.GaugeId = gauge
+		expectedPoolToGauges.PoolToGauge = append(expectedPoolToGauges.PoolToGauge, poolToGauge)
+	}
+
+	genesisExported := suite.App.PoolIncentivesKeeper.ExportGenesis(ctx)
+	suite.Equal(genesisExported.Params, genesis.Params)
+	suite.Equal(genesisExported.LockableDurations, durations)
+	suite.Equal(genesisExported.DistrInfo, genesis.DistrInfo)
+	suite.Equal(genesisExported.PoolToGauges, &expectedPoolToGauges)
 }
diff --git a/x/superfluid/keeper/distribution_test.go b/x/superfluid/keeper/distribution_test.go
index da1651ada..0c379f882 100644
--- a/x/superfluid/keeper/distribution_test.go
+++ b/x/superfluid/keeper/distribution_test.go
@@ -47,14 +47,14 @@ func (suite *KeeperTestSuite) TestMoveSuperfluidDelegationRewardToGauges() {
 			[]int64{0},
 			[]gaugeChecker{{0, 0, 0, true}},
 		},
-		// In this case, allocate reward to validators with different stat. 
+		// In this case, allocate reward to validators with different stat.
 		// There is no difference between Bonded, Unbonding, Unbonded
 		{
 			"add unbonded, unbonding validator case",
 			[]stakingtypes.BondStatus{stakingtypes.Bonded, stakingtypes.Unbonded, stakingtypes.Unbonding},
 			3,
 			[]superfluidDelegation{{0, 0, 0, 1000000}, {1, 1, 0, 1000000}, {2, 2, 0, 1000000}},
-			[]int64{0,1,2},
+			[]int64{0, 1, 2},
 			[]gaugeChecker{{0, 0, 0, true}, {1, 1, 0, true}, {2, 2, 0, true}},
 		},
 		// Do not allocate rewards to the Unbonded validator. Therefore gauges are not distributed
diff --git a/x/superfluid/keeper/stake_test.go b/x/superfluid/keeper/stake_test.go
index c5df55ec5..3cee0f0ad 100644
--- a/x/superfluid/keeper/stake_test.go
+++ b/x/superfluid/keeper/stake_test.go
@@ -322,10 +322,10 @@ func (suite *KeeperTestSuite) TestSuperfluidUndelegate() {
 }
 
 // TestSuperfluidUnbondLock tests the following.
-// 		1. test SuperfluidUnbondLock does not work before undelegation
-// 		2. test SuperfluidUnbondLock makes underlying lock start unlocking
-// 		3. test that synthetic lockup being finished does not mean underlying lock is finished
-//      4. test after SuperfluidUnbondLock + lockup time, the underlying lock is finished
+//  1. test SuperfluidUnbondLock does not work before undelegation
+//  2. test SuperfluidUnbondLock makes underlying lock start unlocking
+//  3. test that synthetic lockup being finished does not mean underlying lock is finished
+//  4. test after SuperfluidUnbondLock + lockup time, the underlying lock is finished
 func (suite *KeeperTestSuite) TestSuperfluidUnbondLock() {
 	suite.SetupTest()
 
@@ -542,7 +542,6 @@ func (suite *KeeperTestSuite) TestRefreshIntermediaryDelegationAmounts() {
 				refreshed := suite.App.BankKeeper.GetBalance(suite.Ctx, intermediaryAcc.GetAccAddress(), sdk.DefaultBondDenom)
 				suite.Require().True(refreshed.IsZero())
 			}
-
 		})
 	}
 }
diff --git a/x/tokenfactory/keeper/admins_test.go b/x/tokenfactory/keeper/admins_test.go
index df28e50a5..dc0e92a58 100644
--- a/x/tokenfactory/keeper/admins_test.go
+++ b/x/tokenfactory/keeper/admins_test.go
@@ -274,7 +274,6 @@ func (suite *KeeperTestSuite) TestChangeAdminDenom() {
 }
 
 func (suite *KeeperTestSuite) TestSetDenomMetaData() {
-
 	// setup test
 	suite.SetupTest()
 	suite.CreateDefaultDenom()
diff --git a/x/twap/export_test.go b/x/twap/export_test.go
index f521dd050..f4cf043f7 100644
--- a/x/twap/export_test.go
+++ b/x/twap/export_test.go
@@ -76,6 +76,16 @@ func NewTwapRecord(k types.AmmInterface, ctx sdk.Context, poolId uint64, denom0,
 	return newTwapRecord(k, ctx, poolId, denom0, denom1)
 }
 
+func GetSpotPrices(
+	ctx sdk.Context,
+	k types.AmmInterface,
+	poolId uint64,
+	denom0, denom1 string,
+	previousErrorTime time.Time,
+) (sp0 sdk.Dec, sp1 sdk.Dec, latestErrTime time.Time) {
+	return getSpotPrices(ctx, k, poolId, denom0, denom1, previousErrorTime)
+}
+
 func (k *Keeper) GetAmmInterface() types.AmmInterface {
 	return k.ammkeeper
 }
diff --git a/x/twap/keeper_test.go b/x/twap/keeper_test.go
index e9b397d83..19837037d 100644
--- a/x/twap/keeper_test.go
+++ b/x/twap/keeper_test.go
@@ -152,6 +152,7 @@ func withSp0(twap types.TwapRecord, sp sdk.Dec) types.TwapRecord {
 	twap.P0LastSpotPrice = sp
 	return twap
 }
+
 func withSp1(twap types.TwapRecord, sp sdk.Dec) types.TwapRecord {
 	twap.P1LastSpotPrice = sp
 	return twap
@@ -342,16 +343,18 @@ func (s *TestSuite) validateExpectedRecords(expectedRecords []types.TwapRecord)
 	s.Require().ElementsMatch(poolIndexedTwaps, expectedRecords)
 }
 
-// createTestRecordsFromTime creates and returns 4 test records in the following order:
-// - at time t - 2 seconds, with pool id 0
-// - at time t - 1 seconds, with pool id 1
-// - at time t, with pool id 2
-// - at time t + 1 seconds, with pool id 3
-func (s *TestSuite) createTestRecordsFromTime(t time.Time) (types.TwapRecord, types.TwapRecord, types.TwapRecord, types.TwapRecord) {
+// createTestRecordsFromTime creates and returns 6 test records in the following order:
+// - 1 record at time t - 2 seconds, with pool id 1
+// - 3 records at time t - 1 seconds, with pool id 2 (3 asset pool)
+// - 1 record at time t, with pool id 3
+// - 1 record at time t + 1 seconds, with pool id 3
+func (s *TestSuite) createTestRecordsFromTime(t time.Time) (types.TwapRecord, types.TwapRecord, types.TwapRecord, types.TwapRecord, types.TwapRecord, types.TwapRecord) {
 	baseRecord := newEmptyPriceRecord(basePoolId, t, denom0, denom1)
 
 	tMin1 := t.Add(-time.Second)
-	tMin1Record := newEmptyPriceRecord(basePoolId+1, tMin1, denom0, denom1)
+	tMin1RecordAB := newEmptyPriceRecord(basePoolId+1, tMin1, denom0, denom1)
+	tMin1RecordAC := newEmptyPriceRecord(basePoolId+1, tMin1, denom0, denom2)
+	tMin1RecordBC := newEmptyPriceRecord(basePoolId+1, tMin1, denom1, denom2)
 
 	tMin2 := t.Add(-time.Second * 2)
 	tMin2Record := newEmptyPriceRecord(basePoolId+2, tMin2, denom0, denom1)
@@ -359,22 +362,38 @@ func (s *TestSuite) createTestRecordsFromTime(t time.Time) (types.TwapRecord, ty
 	tPlus1 := t.Add(time.Second)
 	tPlus1Record := newEmptyPriceRecord(basePoolId+3, tPlus1, denom0, denom1)
 
-	return tMin2Record, tMin1Record, baseRecord, tPlus1Record
+	return tMin2Record, tMin1RecordAB, tMin1RecordAC, tMin1RecordBC, baseRecord, tPlus1Record
 }
 
-// createTestRecordsFromTimeInPool creates and returns 4 test records in the following order:
-// - at time t - 2 seconds
-// - at time t - 1 seconds
-// - at time t
-// - at time t + 1 seconds
+// createTestRecordsFromTimeInPool creates and returns 12 test records in the following order:
+// - 3 records at time t - 2 seconds
+// - 3 records at time t - 1 seconds
+// - 3 records at time t
+// - 3 records t time t + 1 seconds
 // all returned records belong to the same pool with poolId
-func (s *TestSuite) createTestRecordsFromTimeInPool(t time.Time, poolId uint64) (types.TwapRecord, types.TwapRecord, types.TwapRecord, types.TwapRecord) {
-	min2SecRecord, min1SecRecord, baseRecord, plus1SecRecord := s.createTestRecordsFromTime(t)
-	min2SecRecord.PoolId = poolId
-	min1SecRecord.PoolId = poolId
-	baseRecord.PoolId = poolId
-	plus1SecRecord.PoolId = poolId
-	return min2SecRecord, min1SecRecord, baseRecord, plus1SecRecord
+func (s *TestSuite) createTestRecordsFromTimeInPool(t time.Time, poolId uint64) (types.TwapRecord, types.TwapRecord, types.TwapRecord, types.TwapRecord, types.TwapRecord, types.TwapRecord,
+	types.TwapRecord, types.TwapRecord, types.TwapRecord, types.TwapRecord, types.TwapRecord, types.TwapRecord,
+) {
+	baseRecordAB := newEmptyPriceRecord(poolId, t, denom0, denom1)
+	baseRecordAC := newEmptyPriceRecord(poolId, t, denom0, denom2)
+	baseRecordBC := newEmptyPriceRecord(poolId, t, denom1, denom2)
+
+	tMin1 := t.Add(-time.Second)
+	tMin1RecordAB := newEmptyPriceRecord(poolId, tMin1, denom0, denom1)
+	tMin1RecordAC := newEmptyPriceRecord(poolId, tMin1, denom0, denom2)
+	tMin1RecordBC := newEmptyPriceRecord(poolId, tMin1, denom1, denom2)
+
+	tMin2 := t.Add(-time.Second * 2)
+	tMin2RecordAB := newEmptyPriceRecord(poolId, tMin2, denom0, denom1)
+	tMin2RecordAC := newEmptyPriceRecord(poolId, tMin2, denom0, denom2)
+	tMin2RecordBC := newEmptyPriceRecord(poolId, tMin2, denom1, denom2)
+
+	tPlus1 := t.Add(time.Second)
+	tPlus1RecordAB := newEmptyPriceRecord(poolId, tPlus1, denom0, denom1)
+	tPlus1RecordAC := newEmptyPriceRecord(poolId, tPlus1, denom0, denom2)
+	tPlus1RecordBC := newEmptyPriceRecord(poolId, tPlus1, denom1, denom2)
+
+	return tMin2RecordAB, tMin2RecordAC, tMin2RecordBC, tMin1RecordAB, tMin1RecordAC, tMin1RecordBC, baseRecordAB, baseRecordAC, baseRecordBC, tPlus1RecordAB, tPlus1RecordAC, tPlus1RecordBC
 }
 
 // newTwoAssetPoolTwapRecordWithDefaults creates a single twap records, mimicking what one would expect from a two asset pool.
@@ -438,6 +457,104 @@ func newEmptyPriceRecord(poolId uint64, t time.Time, asset0 string, asset1 strin
 	}
 }
 
+func newRecord(poolId uint64, t time.Time, sp0, accum0, accum1 sdk.Dec) types.TwapRecord {
+	return types.TwapRecord{
+		PoolId:          poolId,
+		Asset0Denom:     defaultTwoAssetCoins[0].Denom,
+		Asset1Denom:     defaultTwoAssetCoins[1].Denom,
+		Time:            t,
+		P0LastSpotPrice: sp0,
+		P1LastSpotPrice: sdk.OneDec().Quo(sp0),
+		// make new copies
+		P0ArithmeticTwapAccumulator: accum0.Add(sdk.ZeroDec()),
+		P1ArithmeticTwapAccumulator: accum1.Add(sdk.ZeroDec()),
+	}
+}
+
+// make an expected record for math tests, we adjust other values in the test runner.
+func newExpRecord(accum0, accum1 sdk.Dec) types.TwapRecord {
+	return types.TwapRecord{
+		Asset0Denom: defaultTwoAssetCoins[0].Denom,
+		Asset1Denom: defaultTwoAssetCoins[1].Denom,
+		// make new copies
+		P0ArithmeticTwapAccumulator: accum0.Add(sdk.ZeroDec()),
+		P1ArithmeticTwapAccumulator: accum1.Add(sdk.ZeroDec()),
+	}
+}
+
+func newThreeAssetRecord(poolId uint64, t time.Time, sp0, accumA, accumB, accumC sdk.Dec) []types.TwapRecord {
+	spA := sp0
+	spB := sdk.OneDec().Quo(sp0)
+	spC := sp0.Mul(sdk.NewDec(2))
+	twapAB := types.TwapRecord{
+		PoolId:          poolId,
+		Asset0Denom:     defaultThreeAssetCoins[0].Denom,
+		Asset1Denom:     defaultThreeAssetCoins[1].Denom,
+		Time:            t,
+		P0LastSpotPrice: spA,
+		P1LastSpotPrice: spB,
+		// make new copies
+		P0ArithmeticTwapAccumulator: accumA.Add(sdk.ZeroDec()),
+		P1ArithmeticTwapAccumulator: accumB.Add(sdk.ZeroDec()),
+	}
+	twapAC := twapAB
+	twapAC.Asset1Denom = denom2
+	twapAC.P1LastSpotPrice = spC
+	twapAC.P1ArithmeticTwapAccumulator = accumC
+	twapBC := twapAC
+	twapBC.Asset0Denom = denom1
+	twapBC.P0LastSpotPrice = spB
+	twapBC.P0ArithmeticTwapAccumulator = accumB
+	return []types.TwapRecord{twapAB, twapAC, twapBC}
+}
+
+// make an expected record for math tests, we adjust other values in the test runner.
+func newThreeAssetExpRecord(poolId uint64, accumA, accumB, accumC sdk.Dec) []types.TwapRecord {
+	twapAB := types.TwapRecord{
+		PoolId:      poolId,
+		Asset0Denom: defaultThreeAssetCoins[0].Denom,
+		Asset1Denom: defaultThreeAssetCoins[1].Denom,
+		// make new copies
+		P0ArithmeticTwapAccumulator: accumA.Add(sdk.ZeroDec()),
+		P1ArithmeticTwapAccumulator: accumB.Add(sdk.ZeroDec()),
+	}
+	twapAC := twapAB
+	twapAC.Asset1Denom = denom2
+	twapAC.P1ArithmeticTwapAccumulator = accumC
+	twapBC := twapAC
+	twapBC.Asset0Denom = denom1
+	twapBC.P0ArithmeticTwapAccumulator = accumB
+	return []types.TwapRecord{twapAB, twapAC, twapBC}
+}
+
+func newOneSidedRecord(time time.Time, accum sdk.Dec, useP0 bool) types.TwapRecord {
+	record := types.TwapRecord{Time: time, Asset0Denom: denom0, Asset1Denom: denom1}
+	if useP0 {
+		record.P0ArithmeticTwapAccumulator = accum
+	} else {
+		record.P1ArithmeticTwapAccumulator = accum
+	}
+	record.P0LastSpotPrice = sdk.ZeroDec()
+	record.P1LastSpotPrice = sdk.OneDec()
+	return record
+}
+
+func newThreeAssetOneSidedRecord(time time.Time, accum sdk.Dec, useP0 bool) []types.TwapRecord {
+	record := types.TwapRecord{Time: time, Asset0Denom: denom0, Asset1Denom: denom1}
+	if useP0 {
+		record.P0ArithmeticTwapAccumulator = accum
+	} else {
+		record.P1ArithmeticTwapAccumulator = accum
+	}
+	record.P0LastSpotPrice = sdk.ZeroDec()
+	record.P1LastSpotPrice = sdk.OneDec()
+	records := []types.TwapRecord{record, record, record}
+	records[1].Asset1Denom = denom2
+	records[2].Asset0Denom = denom1
+	records[2].Asset1Denom = denom2
+	return records
+}
+
 func recordWithUpdatedAccum(record types.TwapRecord, accum0 sdk.Dec, accum1 sdk.Dec) types.TwapRecord {
 	record.P0ArithmeticTwapAccumulator = accum0
 	record.P1ArithmeticTwapAccumulator = accum1
diff --git a/x/twap/logic_test.go b/x/twap/logic_test.go
index b1d776454..d7fe7b525 100644
--- a/x/twap/logic_test.go
+++ b/x/twap/logic_test.go
@@ -16,33 +16,72 @@ import (
 	"github.com/osmosis-labs/osmosis/v12/x/twap/types/twapmock"
 )
 
-var zeroDec = sdk.ZeroDec()
-var oneDec = sdk.OneDec()
-var twoDec = oneDec.Add(oneDec)
-var OneSec = sdk.MustNewDecFromStr("1000.000000000000000000")
-
-func newRecord(poolId uint64, t time.Time, sp0, accum0, accum1 sdk.Dec) types.TwapRecord {
-	return types.TwapRecord{
-		PoolId:          poolId,
-		Asset0Denom:     defaultTwoAssetCoins[0].Denom,
-		Asset1Denom:     defaultTwoAssetCoins[1].Denom,
-		Time:            t,
-		P0LastSpotPrice: sp0,
-		P1LastSpotPrice: sdk.OneDec().Quo(sp0),
-		// make new copies
-		P0ArithmeticTwapAccumulator: accum0.Add(sdk.ZeroDec()),
-		P1ArithmeticTwapAccumulator: accum1.Add(sdk.ZeroDec()),
+var (
+	zeroDec = sdk.ZeroDec()
+	oneDec  = sdk.OneDec()
+	twoDec  = oneDec.Add(oneDec)
+	OneSec  = sdk.MustNewDecFromStr("1000.000000000000000000")
+)
+
+func (s *TestSuite) TestGetSpotPrices() {
+	currTime := time.Now()
+	poolID := s.PrepareBalancerPoolWithCoins(defaultTwoAssetCoins...)
+	mockAMMI := twapmock.NewProgrammedAmmInterface(s.App.TwapKeeper.GetAmmInterface())
+	s.App.TwapKeeper.SetAmmInterface(mockAMMI)
+
+	ctx := s.Ctx.WithBlockTime(currTime.Add(5 * time.Second))
+
+	testCases := map[string]struct {
+		poolID                uint64
+		prevErrTime           time.Time
+		mockSp0               sdk.Dec
+		mockSp1               sdk.Dec
+		mockSp0Err            error
+		mockSp1Err            error
+		expectedSp0           sdk.Dec
+		expectedSp1           sdk.Dec
+		expectedLatestErrTime time.Time
+	}{
+		"zero sp": {
+			poolID:                poolID,
+			prevErrTime:           currTime,
+			mockSp0:               sdk.ZeroDec(),
+			mockSp1:               sdk.ZeroDec(),
+			mockSp0Err:            fmt.Errorf("foo"),
+			expectedSp0:           sdk.ZeroDec(),
+			expectedSp1:           sdk.ZeroDec(),
+			expectedLatestErrTime: ctx.BlockTime(),
+		},
+		"exceeds max spot price": {
+			poolID:                poolID,
+			prevErrTime:           currTime,
+			mockSp0:               types.MaxSpotPrice.Add(sdk.OneDec()),
+			mockSp1:               types.MaxSpotPrice.Add(sdk.OneDec()),
+			expectedSp0:           types.MaxSpotPrice,
+			expectedSp1:           types.MaxSpotPrice,
+			expectedLatestErrTime: ctx.BlockTime(),
+		},
+		"valid spot prices": {
+			poolID:                poolID,
+			prevErrTime:           currTime,
+			mockSp0:               sdk.NewDecWithPrec(55, 2),
+			mockSp1:               sdk.NewDecWithPrec(6, 1),
+			expectedSp0:           sdk.NewDecWithPrec(55, 2),
+			expectedSp1:           sdk.NewDecWithPrec(6, 1),
+			expectedLatestErrTime: currTime,
+		},
 	}
-}
 
-// make an expected record for math tests, we adjust other values in the test runner.
-func newExpRecord(accum0, accum1 sdk.Dec) types.TwapRecord {
-	return types.TwapRecord{
-		Asset0Denom: defaultTwoAssetCoins[0].Denom,
-		Asset1Denom: defaultTwoAssetCoins[1].Denom,
-		// make new copies
-		P0ArithmeticTwapAccumulator: accum0.Add(sdk.ZeroDec()),
-		P1ArithmeticTwapAccumulator: accum1.Add(sdk.ZeroDec()),
+	for name, tc := range testCases {
+		s.Run(name, func() {
+			mockAMMI.ProgramPoolSpotPriceOverride(tc.poolID, denom0, denom1, tc.mockSp0, tc.mockSp0Err)
+			mockAMMI.ProgramPoolSpotPriceOverride(tc.poolID, denom1, denom0, tc.mockSp1, tc.mockSp1Err)
+
+			sp0, sp1, latestErrTime := twap.GetSpotPrices(ctx, mockAMMI, tc.poolID, denom0, denom1, tc.prevErrTime)
+			s.Require().Equal(tc.expectedSp0, sp0)
+			s.Require().Equal(tc.expectedSp1, sp1)
+			s.Require().Equal(tc.expectedLatestErrTime, latestErrTime)
+		})
 	}
 }
 
@@ -104,7 +143,6 @@ func (s *TestSuite) TestNewTwapRecord() {
 				s.Require().Equal(sdk.ZeroDec(), twapRecord.P0ArithmeticTwapAccumulator)
 				s.Require().Equal(sdk.ZeroDec(), twapRecord.P1ArithmeticTwapAccumulator)
 			}
-
 		})
 	}
 }
@@ -239,6 +277,45 @@ func TestRecordWithUpdatedAccumulators(t *testing.T) {
 	}
 }
 
+func TestRecordWithUpdatedAccumulators_ThreeAsset(t *testing.T) {
+	poolId := uint64(2)
+	tests := map[string]struct {
+		record          []types.TwapRecord
+		interpolateTime time.Time
+		expRecord       []types.TwapRecord
+	}{
+		"accum with zero value": {
+			record:          newThreeAssetRecord(poolId, time.Unix(1, 0), sdk.NewDec(10), zeroDec, zeroDec, zeroDec),
+			interpolateTime: time.Unix(2, 0),
+			expRecord:       newThreeAssetExpRecord(poolId, OneSec.MulInt64(10), OneSec.QuoInt64(10), OneSec.MulInt64(20)),
+		},
+		"small starting accumulators": {
+			record:          newThreeAssetRecord(poolId, time.Unix(1, 0), sdk.NewDec(10), twoDec, oneDec, twoDec),
+			interpolateTime: time.Unix(2, 0),
+			expRecord:       newThreeAssetExpRecord(poolId, twoDec.Add(OneSec.MulInt64(10)), oneDec.Add(OneSec.QuoInt64(10)), twoDec.Add(OneSec.MulInt64(20))),
+		},
+		"larger time interval": {
+			record:          newThreeAssetRecord(poolId, time.Unix(11, 0), sdk.NewDec(10), twoDec, oneDec, twoDec),
+			interpolateTime: time.Unix(55, 0),
+			expRecord:       newThreeAssetExpRecord(poolId, twoDec.Add(OneSec.MulInt64(44*10)), oneDec.Add(OneSec.MulInt64(44).QuoInt64(10)), twoDec.Add(OneSec.MulInt64(44*20))),
+		},
+	}
+
+	for name, test := range tests {
+		t.Run(name, func(t *testing.T) {
+			for i := range test.record {
+				// correct expected record based off copy/paste values
+				test.expRecord[i].Time = test.interpolateTime
+				test.expRecord[i].P0LastSpotPrice = test.record[i].P0LastSpotPrice
+				test.expRecord[i].P1LastSpotPrice = test.record[i].P1LastSpotPrice
+
+				gotRecord := twap.RecordWithUpdatedAccumulators(test.record[i], test.interpolateTime)
+				require.Equal(t, test.expRecord[i], gotRecord)
+			}
+		})
+	}
+}
+
 func (s *TestSuite) TestGetInterpolatedRecord() {
 	baseRecord := newTwoAssetPoolTwapRecordWithDefaults(baseTime, sdk.OneDec(), sdk.OneDec(), sdk.OneDec())
 
@@ -319,16 +396,75 @@ func (s *TestSuite) TestGetInterpolatedRecord() {
 	}
 }
 
-func newOneSidedRecord(time time.Time, accum sdk.Dec, useP0 bool) types.TwapRecord {
-	record := types.TwapRecord{Time: time, Asset0Denom: denom0, Asset1Denom: denom1}
-	if useP0 {
-		record.P0ArithmeticTwapAccumulator = accum
-	} else {
-		record.P1ArithmeticTwapAccumulator = accum
+func (s *TestSuite) TestGetInterpolatedRecord_ThreeAsset() {
+	baseRecord := newThreeAssetRecord(2, baseTime, sdk.NewDec(10), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec())
+	// all tests occur with updateTime = base time + time.Unix(1, 0)
+	tests := map[string]struct {
+		recordsToPreSet       []types.TwapRecord
+		testTime              time.Time
+		expectedP0Accumulator []sdk.Dec
+		expectedP1Accumulator []sdk.Dec
+		expectedErr           error
+	}{
+		"call 1 second after existing record": {
+			recordsToPreSet: baseRecord,
+			testTime:        baseTime.Add(time.Second),
+			// P0 and P1 TwapAccumulators both start at 0
+			// A 10 spot price * 1000ms = 10000
+			// A 10 spot price * 1000ms = 10000
+			// B .1 spot price * 1000ms = 100
+			expectedP0Accumulator: []sdk.Dec{
+				baseRecord[0].P0ArithmeticTwapAccumulator.Add(sdk.NewDec(10000)),
+				baseRecord[1].P0ArithmeticTwapAccumulator.Add(sdk.NewDec(10000)),
+				baseRecord[2].P0ArithmeticTwapAccumulator.Add(sdk.NewDec(100)),
+			},
+			// B .1 spot price * 1000ms = 100
+			// C 20 spot price * 1000ms = 20000
+			// C 20 spot price * 1000ms = 20000
+			expectedP1Accumulator: []sdk.Dec{
+				baseRecord[0].P1ArithmeticTwapAccumulator.Add(sdk.NewDec(100)),
+				baseRecord[1].P1ArithmeticTwapAccumulator.Add(sdk.NewDec(20000)),
+				baseRecord[2].P1ArithmeticTwapAccumulator.Add(sdk.NewDec(20000)),
+			},
+		},
+		"call 1 second before existing record": {
+			recordsToPreSet: baseRecord,
+			testTime:        baseTime.Add(-time.Second),
+			expectedErr: fmt.Errorf("looking for a time thats too old, not in the historical index. "+
+				" Try storing the accumulator value. (requested time %s)", baseTime.Add(-time.Second)),
+		},
+		"test non lexicographical order parameter": {
+			recordsToPreSet: baseRecord,
+			testTime:        baseTime,
+		},
+	}
+
+	for name, test := range tests {
+		s.Run(name, func() {
+			s.SetupTest()
+			for i := range test.recordsToPreSet {
+				s.twapkeeper.StoreNewRecord(s.Ctx, test.recordsToPreSet[i])
+
+				interpolatedRecord, err := s.twapkeeper.GetInterpolatedRecord(s.Ctx, baseRecord[i].PoolId, baseRecord[i].Asset0Denom, baseRecord[i].Asset1Denom, test.testTime)
+				if test.expectedErr != nil {
+					s.Require().Error(err)
+					s.Require().Equal(test.expectedErr.Error(), err.Error())
+					return
+				}
+				s.Require().NoError(err)
+
+				if test.testTime.Equal(baseTime) {
+					s.Require().Equal(test.recordsToPreSet[i], interpolatedRecord)
+				} else {
+					s.Require().Equal(test.testTime, interpolatedRecord.Time)
+					s.Require().Equal(test.recordsToPreSet[i].P0LastSpotPrice, interpolatedRecord.P0LastSpotPrice)
+					s.Require().Equal(test.recordsToPreSet[i].P1LastSpotPrice, interpolatedRecord.P1LastSpotPrice)
+					s.Require().Equal(test.expectedP0Accumulator[i], interpolatedRecord.P0ArithmeticTwapAccumulator)
+					s.Require().Equal(test.expectedP1Accumulator[i], interpolatedRecord.P1ArithmeticTwapAccumulator)
+				}
+			}
+		})
 	}
-	record.P0LastSpotPrice = sdk.ZeroDec()
-	record.P1LastSpotPrice = sdk.OneDec()
-	return record
 }
 
 type computeArithmeticTwapTestCase struct {
@@ -339,6 +475,14 @@ type computeArithmeticTwapTestCase struct {
 	expErr      bool
 }
 
+type computeThreeAssetArithmeticTwapTestCase struct {
+	startRecord []types.TwapRecord
+	endRecord   []types.TwapRecord
+	quoteAsset  []string
+	expTwap     []sdk.Dec
+	expErr      bool
+}
+
 // TestComputeArithmeticTwap tests ComputeArithmeticTwap on various inputs.
 // The test vectors are structured by setting up different start and records,
 // based on time interval, and their accumulator values.
@@ -417,6 +561,52 @@ func TestComputeArithmeticTwap(t *testing.T) {
 	}
 }
 
+func TestComputeArithmeticTwap_ThreeAsset(t *testing.T) {
+	testThreeAssetCaseFromDeltas := func(startAccum, accumDiff sdk.Dec, timeDelta time.Duration, expectedTwap sdk.Dec) computeThreeAssetArithmeticTwapTestCase {
+		return computeThreeAssetArithmeticTwapTestCase{
+			newThreeAssetOneSidedRecord(baseTime, startAccum, true),
+			newThreeAssetOneSidedRecord(baseTime.Add(timeDelta), startAccum.Add(accumDiff), true),
+			[]string{denom0, denom0, denom1},
+			[]sdk.Dec{expectedTwap, expectedTwap, expectedTwap},
+			false,
+		}
+	}
+
+	tenSecAccum := OneSec.MulInt64(10)
+	pointOneAccum := OneSec.QuoInt64(10)
+	tests := map[string]computeThreeAssetArithmeticTwapTestCase{
+		"three asset basic: spot price = 1 for one second, 0 init accumulator": {
+			startRecord: newThreeAssetOneSidedRecord(baseTime, sdk.ZeroDec(), true),
+			endRecord:   newThreeAssetOneSidedRecord(tPlusOne, OneSec, true),
+			quoteAsset:  []string{denom0, denom0, denom1},
+			expTwap:     []sdk.Dec{sdk.OneDec(), sdk.OneDec(), sdk.OneDec()},
+		},
+		"three asset same record: asset1, end spot price = 1": {
+			startRecord: newThreeAssetOneSidedRecord(baseTime, sdk.ZeroDec(), true),
+			endRecord:   newThreeAssetOneSidedRecord(baseTime, sdk.ZeroDec(), true),
+			quoteAsset:  []string{denom1, denom2, denom2},
+			expTwap:     []sdk.Dec{sdk.OneDec(), sdk.OneDec(), sdk.OneDec()},
+		},
+		"three asset. accumulator = 10*OneSec, t=5s. 0 base accum": testThreeAssetCaseFromDeltas(
+			sdk.ZeroDec(), tenSecAccum, 5*time.Second, sdk.NewDec(2)),
+
+		// test that base accum has no impact
+		"three asset. accumulator = 10*OneSec, t=5s. 10 base accum": testThreeAssetCaseFromDeltas(
+			sdk.NewDec(10), tenSecAccum, 5*time.Second, sdk.NewDec(2)),
+		"three asset. accumulator = 10*OneSec, t=100s. .1*second base accum": testThreeAssetCaseFromDeltas(
+			pointOneAccum, tenSecAccum, 100*time.Second, sdk.NewDecWithPrec(1, 1)),
+	}
+	for name, test := range tests {
+		t.Run(name, func(t *testing.T) {
+			for i, startRec := range test.startRecord {
+				actualTwap, err := twap.ComputeArithmeticTwap(startRec, test.endRecord[i], test.quoteAsset[i])
+				require.Equal(t, test.expTwap[i], actualTwap)
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
 // This tests the behavior of computeArithmeticTwap, around error returning
 // when there has been an intermediate spot price error.
 func TestComputeArithmeticTwapWithSpotPriceError(t *testing.T) {
@@ -475,24 +665,24 @@ func (s *TestSuite) TestPruneRecords() {
 	recordHistoryKeepPeriod := s.twapkeeper.RecordHistoryKeepPeriod(s.Ctx)
 
 	pool1OlderMin2MsRecord, // deleted
-		pool2OlderMin1MsRecord,  // deleted
-		pool3OlderBaseRecord,    // kept as newest under keep period
+		pool2OlderMin1MsRecordAB, pool2OlderMin1MsRecordAC, pool2OlderMin1MsRecordBC, // deleted
+		pool3OlderBaseRecord, // kept as newest under keep period
 		pool4OlderPlus1Record := // kept as newest under keep period
-		s.createTestRecordsFromTime(baseTime.Add(2 * -recordHistoryKeepPeriod))
+	s.createTestRecordsFromTime(baseTime.Add(2 * -recordHistoryKeepPeriod))
 
 	pool1Min2MsRecord, // kept as newest under keep period
-		pool2Min1MsRecord,  // kept as newest under keep period
-		pool3BaseRecord,    // kept as it is at the keep period boundary
+		pool2Min1MsRecordAB, pool2Min1MsRecordAC, pool2Min1MsRecordBC, // kept as newest under keep period
+		pool3BaseRecord, // kept as it is at the keep period boundary
 		pool4Plus1Record := // kept as it is above the keep period boundary
-		s.createTestRecordsFromTime(baseTime.Add(-recordHistoryKeepPeriod))
+	s.createTestRecordsFromTime(baseTime.Add(-recordHistoryKeepPeriod))
 
 	// non-ascending insertion order.
 	recordsToPreSet := []types.TwapRecord{
-		pool2OlderMin1MsRecord,
+		pool2OlderMin1MsRecordAB, pool2OlderMin1MsRecordAC, pool2OlderMin1MsRecordBC,
 		pool4Plus1Record,
 		pool4OlderPlus1Record,
 		pool3OlderBaseRecord,
-		pool2Min1MsRecord,
+		pool2Min1MsRecordAB, pool2Min1MsRecordAC, pool2Min1MsRecordBC,
 		pool3BaseRecord,
 		pool1Min2MsRecord,
 		pool1OlderMin2MsRecord,
@@ -505,7 +695,7 @@ func (s *TestSuite) TestPruneRecords() {
 		pool3OlderBaseRecord,
 		pool4OlderPlus1Record,
 		pool1Min2MsRecord,
-		pool2Min1MsRecord,
+		pool2Min1MsRecordAB, pool2Min1MsRecordAC, pool2Min1MsRecordBC,
 		pool3BaseRecord,
 		pool4Plus1Record,
 	}
@@ -549,7 +739,7 @@ func (s *TestSuite) TestUpdateRecords() {
 		isMostRecent  bool
 	}
 
-	var spError = errors.New("spot price error")
+	spError := errors.New("spot price error")
 
 	validateRecords := func(expectedRecords []expectedResults, actualRecords []types.TwapRecord) {
 		s.Require().Equal(len(expectedRecords), len(actualRecords))
diff --git a/x/twap/store_test.go b/x/twap/store_test.go
index bd13058cd..6a7ea8cd2 100644
--- a/x/twap/store_test.go
+++ b/x/twap/store_test.go
@@ -92,7 +92,8 @@ func (s *TestSuite) TestGetAllMostRecentRecordsForPool() {
 				withPoolId(baseRecord, 2),
 				withPoolId(baseRecord, 10),
 				withPoolId(baseRecord, 11),
-				withPoolId(baseRecord, 20)},
+				withPoolId(baseRecord, 20),
+			},
 			poolId:          1,
 			expectedRecords: []types.TwapRecord{baseRecord},
 		},
@@ -102,7 +103,8 @@ func (s *TestSuite) TestGetAllMostRecentRecordsForPool() {
 				withPoolId(baseRecord, 10),
 				withPoolId(baseRecord, 11),
 				withPoolId(baseRecord, 19),
-				withPoolId(baseRecord, 90)},
+				withPoolId(baseRecord, 90),
+			},
 			poolId:          9,
 			expectedRecords: []types.TwapRecord{withPoolId(baseRecord, 9)},
 		},
@@ -110,23 +112,27 @@ func (s *TestSuite) TestGetAllMostRecentRecordsForPool() {
 			recordsToSet: []types.TwapRecord{
 				newEmptyPriceRecord(1, baseTime, denom0, denom1),
 				newEmptyPriceRecord(1, baseTime, denom0, denom2),
-				newEmptyPriceRecord(1, baseTime, denom1, denom2)},
+				newEmptyPriceRecord(1, baseTime, denom1, denom2),
+			},
 			poolId: 1,
 			expectedRecords: []types.TwapRecord{
 				newEmptyPriceRecord(1, baseTime, denom0, denom1),
 				newEmptyPriceRecord(1, baseTime, denom0, denom2),
-				newEmptyPriceRecord(1, baseTime, denom1, denom2)},
+				newEmptyPriceRecord(1, baseTime, denom1, denom2),
+			},
 		},
 		"set multi-asset pool record - reverse order": {
 			recordsToSet: []types.TwapRecord{
 				newEmptyPriceRecord(1, baseTime, denom0, denom2),
 				newEmptyPriceRecord(1, baseTime, denom1, denom2),
-				newEmptyPriceRecord(1, baseTime, denom0, denom1)},
+				newEmptyPriceRecord(1, baseTime, denom0, denom1),
+			},
 			poolId: 1,
 			expectedRecords: []types.TwapRecord{
 				newEmptyPriceRecord(1, baseTime, denom0, denom1),
 				newEmptyPriceRecord(1, baseTime, denom0, denom2),
-				newEmptyPriceRecord(1, baseTime, denom1, denom2)},
+				newEmptyPriceRecord(1, baseTime, denom1, denom2),
+			},
 		},
 	}
 
@@ -175,42 +181,53 @@ func (s *TestSuite) TestGetRecordAtOrBeforeTime() {
 		"rev at latest (exact)": {[]types.TwapRecord{baseRecord}, defaultRevInputAt(baseTime), baseRecord, nil},
 
 		"get latest (exact) w/ past entries": {
-			[]types.TwapRecord{tMin1Record, baseRecord}, defaultInputAt(baseTime), baseRecord, nil},
+			[]types.TwapRecord{tMin1Record, baseRecord}, defaultInputAt(baseTime), baseRecord, nil,
+		},
 		"get entry (exact) w/ a subsequent entry": {
-			[]types.TwapRecord{tMin1Record, baseRecord}, defaultInputAt(tMin1), tMin1Record, nil},
+			[]types.TwapRecord{tMin1Record, baseRecord}, defaultInputAt(tMin1), tMin1Record, nil,
+		},
 		"get sandwitched entry (exact)": {
-			[]types.TwapRecord{tMin1Record, baseRecord, tPlus1Record}, defaultInputAt(baseTime), baseRecord, nil},
+			[]types.TwapRecord{tMin1Record, baseRecord, tPlus1Record}, defaultInputAt(baseTime), baseRecord, nil,
+		},
 		"rev sandwitched entry (exact)": {
-			[]types.TwapRecord{tMin1Record, baseRecord, tPlus1Record}, defaultRevInputAt(baseTime), baseRecord, nil},
+			[]types.TwapRecord{tMin1Record, baseRecord, tPlus1Record}, defaultRevInputAt(baseTime), baseRecord, nil,
+		},
 
 		"get future":                 {[]types.TwapRecord{baseRecord}, defaultInputAt(tPlus1), baseRecord, nil},
 		"get future w/ past entries": {[]types.TwapRecord{tMin1Record, baseRecord}, defaultInputAt(tPlus1), baseRecord, nil},
 
 		"get in between entries (2 entry)": {
 			[]types.TwapRecord{tMin1Record, baseRecord},
-			defaultInputAt(baseTime.Add(-time.Millisecond)), tMin1Record, nil},
+			defaultInputAt(baseTime.Add(-time.Millisecond)), tMin1Record, nil,
+		},
 		"get in between entries (3 entry)": {
 			[]types.TwapRecord{tMin1Record, baseRecord, tPlus1Record},
-			defaultInputAt(baseTime.Add(-time.Millisecond)), tMin1Record, nil},
+			defaultInputAt(baseTime.Add(-time.Millisecond)), tMin1Record, nil,
+		},
 		"get in between entries (3 entry) #2": {
 			[]types.TwapRecord{tMin1Record, baseRecord, tPlus1Record},
-			defaultInputAt(baseTime.Add(time.Millisecond)), baseRecord, nil},
+			defaultInputAt(baseTime.Add(time.Millisecond)), baseRecord, nil,
+		},
 
 		"query too old": {
 			[]types.TwapRecord{tMin1Record, baseRecord, tPlus1Record},
 			defaultInputAt(baseTime.Add(-time.Second * 2)),
-			baseRecord, twap.TimeTooOldError{Time: baseTime.Add(-time.Second * 2)}},
+			baseRecord,
+			twap.TimeTooOldError{Time: baseTime.Add(-time.Second * 2)},
+		},
 
 		"non-existent pool ID": {
 			[]types.TwapRecord{tMin1Record, baseRecord, tPlus1Record},
 			wrongPoolIdInputAt(baseTime), baseRecord, fmt.Errorf(
 				"getTwapRecord: querying for assets %s %s that are not in pool id %d",
-				baseRecord.Asset0Denom, baseRecord.Asset1Denom, 2)},
+				baseRecord.Asset0Denom, baseRecord.Asset1Denom, 2),
+		},
 		"pool2 record get": {
 			recordsToSet:   []types.TwapRecord{newEmptyPriceRecord(2, baseTime, denom0, denom1)},
 			input:          wrongPoolIdInputAt(baseTime),
 			expectedRecord: newEmptyPriceRecord(2, baseTime, denom0, denom1),
-			expErr:         nil},
+			expErr:         nil,
+		},
 	}
 	for name, test := range tests {
 		s.Run(name, func() {
@@ -240,30 +257,29 @@ func (s *TestSuite) TestPruneRecordsBeforeTimeButNewest() {
 	// These are manually created to be able to refer to them by name
 	// for convenience.
 
-	// Create 4 pool records from base time, each in different pool with the difference of 1 second between them
-	pool1Min2SBaseMs, pool2Min1SBaseMs, pool3BaseSecBaseMs, pool4Plus1SBaseMs := s.createTestRecordsFromTime(baseTime)
+	// Create 6 records of 4 pools from base time, each in different pool with the difference of 1 second between them. Pool 2 is a 3 asset pool.
+	pool1Min2SBaseMs, pool2Min1SBaseMsAB, pool2Min1SBaseMsAC, pool2Min1SBaseMsBC, pool3BaseSecBaseMs, pool4Plus1SBaseMs := s.createTestRecordsFromTime(baseTime)
 
-	// Create 4 pool records from base time - 1 ms, each in different pool with the difference of 1 second between them
-	pool1Min2SMin1Ms, pool2Min1SMin1Ms, pool3BaseSecMin1Ms, pool4Plus1SMin1Ms := s.createTestRecordsFromTime(baseTime.Add(-time.Millisecond))
+	// Create 6 records of 4 pools from base time - 1 ms, each in different pool with the difference of 1 second between them. Pool 2 is a 3 asset pool.
+	pool1Min2SMin1Ms, pool2Min1SMin1MsAB, pool2Min1SMin1MsAC, pool2Min1SMin1MsBC, pool3BaseSecMin1Ms, pool4Plus1SMin1Ms := s.createTestRecordsFromTime(baseTime.Add(-time.Millisecond))
 
-	// Create 4 pool records from base time - 2 ms, each in different pool with the difference of 1 second between them
-	pool1Min2SMin2Ms, pool2Min1SMin2Ms, pool3BaseSecMin2Ms, pool4Plus1SMin2Ms := s.createTestRecordsFromTime(baseTime.Add(2 * -time.Millisecond))
+	// Create 6 records of 4 pools from base time - 2 ms, each in different pool with the difference of 1 second between them. Pool 2 is a 3 asset pool.
+	pool1Min2SMin2Ms, pool2Min1SMin2MsAB, pool2Min1SMin2MsAC, pool2Min1SMin2MsBC, pool3BaseSecMin2Ms, pool4Plus1SMin2Ms := s.createTestRecordsFromTime(baseTime.Add(2 * -time.Millisecond))
 
-	// Create 4 pool records from base time - 3 ms, each in different pool with the difference of 1 second between them
-	pool1Min2SMin3Ms, pool2Min1SMin3Ms, pool3BaseSecMin3Ms, pool4Plus1SMin3Ms := s.createTestRecordsFromTime(baseTime.Add(3 * -time.Millisecond))
+	// Create 6 records of 4 pools from base time - 3 ms, each in different pool with the difference of 1 second between them. Pool 2 is a 3 asset pool.
+	pool1Min2SMin3Ms, pool2Min1SMin3MsAB, pool2Min1SMin3MsAC, pool2Min1SMin3MsBC, pool3BaseSecMin3Ms, pool4Plus1SMin3Ms := s.createTestRecordsFromTime(baseTime.Add(3 * -time.Millisecond))
 
-	// Create 4 records in the same pool from base time , each record with the difference of 1 second between them
-	pool5Min2SBaseMs, pool5Min1SBaseMs, pool5BaseSecBaseMs, pool5Plus1SBaseMs := s.createTestRecordsFromTimeInPool(baseTime, 5)
+	// Create 12 records in the same pool from base time , each record with the difference of 1 second between them.
+	pool5Min2SBaseMsAB, pool5Min2SBaseMsAC, pool5Min2SBaseMsBC,
+		pool5Min1SBaseMsAB, pool5Min1SBaseMsAC, pool5Min1SBaseMsBC,
+		pool5BaseSecBaseMsAB, pool5BaseSecBaseMsAC, pool5BaseSecBaseMsBC,
+		pool5Plus1SBaseMsAB, pool5Plus1SBaseMsAC, pool5Plus1SBaseMsBC := s.createTestRecordsFromTimeInPool(baseTime, 5)
 
-	// Create 4 records in the same pool from base time - 1 ms, each record with the difference of 1 second between them
-	pool5Min2SMin1Ms, pool5Min1SMin1Ms, pool5BaseSecMin1Ms, pool5Plus1SMin1Ms := s.createTestRecordsFromTimeInPool(baseTime.Add(-time.Millisecond), 5)
-
-	withDiffPoolAssets := func(twap types.TwapRecord) types.TwapRecord {
-		twap2 := twap
-		twap2.Asset0Denom = "fooDenom"
-		twap2.Asset1Denom = "barDenom"
-		return twap2
-	}
+	// Create 12 records in the same pool from base time - 1 ms, each record with the difference of 1 second between them
+	pool5Min2SMin1MsAB, pool5Min2SMin1MsAC, pool5Min2SMin1MsBC,
+		pool5Min1SMin1MsAB, pool5Min1SMin1MsAC, pool5Min1SMin1MsBC,
+		pool5BaseSecMin1MsAB, pool5BaseSecMin1MsAC, pool5BaseSecMin1MsBC,
+		pool5Plus1SMin1MsAB, pool5Plus1SMin1MsAC, pool5Plus1SMin1MsBC := s.createTestRecordsFromTimeInPool(baseTime.Add(-time.Millisecond), 5)
 
 	tests := map[string]struct {
 		// order does not follow any specific pattern
@@ -286,21 +302,21 @@ func (s *TestSuite) TestPruneRecordsBeforeTimeButNewest() {
 
 			expectedKeptRecords: []types.TwapRecord{pool3BaseSecMin1Ms, pool3BaseSecBaseMs},
 		},
-		"base time - 1s - 2 ms; across pool 2; 4 records; 1 before lastKeptTime; none pruned since newest kept": {
+		"base time - 1s - 2 ms; across pool 2; 12 records; 3 before lastKeptTime; none pruned since newest kept": {
 			recordsToPreSet: []types.TwapRecord{
-				pool2Min1SMin2Ms, // base time - 1s - 2ms; kept since at lastKeptTime
-				pool2Min1SMin1Ms, // base time - 1s - 1ms; kept since older than at lastKeptTime
-				pool2Min1SBaseMs, // base time - 1s; kept since older than lastKeptTime
-				pool2Min1SMin3Ms, // base time - 1s - 3ms; kept since newest before lastKeptTime
+				pool2Min1SMin2MsAB, pool2Min1SMin2MsAC, pool2Min1SMin2MsBC, // base time - 1s - 2ms; kept since at lastKeptTime
+				pool2Min1SMin1MsAB, pool2Min1SMin1MsAC, pool2Min1SMin1MsBC, // base time - 1s - 1ms; kept since older than at lastKeptTime
+				pool2Min1SBaseMsAB, pool2Min1SBaseMsAC, pool2Min1SBaseMsBC, // base time - 1s; kept since older than lastKeptTime
+				pool2Min1SMin3MsAB, pool2Min1SMin3MsAC, pool2Min1SMin3MsBC, // base time - 1s - 3ms; kept since newest before lastKeptTime
 			},
 
 			lastKeptTime: baseTime.Add(-time.Second).Add(2 * -time.Millisecond),
 
 			expectedKeptRecords: []types.TwapRecord{
-				pool2Min1SMin3Ms,
-				pool2Min1SMin2Ms,
-				pool2Min1SMin1Ms,
-				pool2Min1SBaseMs,
+				pool2Min1SMin3MsAB, pool2Min1SMin3MsAC, pool2Min1SMin3MsBC,
+				pool2Min1SMin2MsAB, pool2Min1SMin2MsAC, pool2Min1SMin2MsBC,
+				pool2Min1SMin1MsAB, pool2Min1SMin1MsAC, pool2Min1SMin1MsBC,
+				pool2Min1SBaseMsAB, pool2Min1SBaseMsAC, pool2Min1SBaseMsBC,
 			},
 		},
 		"base time - 2s - 3 ms; across pool 1; 4 records; none before lastKeptTime; none pruned": {
@@ -327,33 +343,33 @@ func (s *TestSuite) TestPruneRecordsBeforeTimeButNewest() {
 
 			expectedKeptRecords: []types.TwapRecord{pool4Plus1SBaseMs},
 		},
-		"base time; across pool 3 and pool 5; pool 3: 4 total records; 3 before lastKeptTime; 2 deleted and newest kept. pool 5: 8 total records; 5 before lastKeptTime; 4 deleted and 1 kept": {
+		"base time; across pool 3 and pool 5; pool 3: 4 total records; 3 before lastKeptTime; 2 deleted and newest 2 kept. pool 5: 24 total records; 12 before lastKeptTime; 12 deleted and 12 kept": {
 			recordsToPreSet: []types.TwapRecord{
 				pool3BaseSecMin3Ms, // base time - 3ms; deleted
 				pool3BaseSecMin2Ms, // base time - 2ms; deleted
 				pool3BaseSecMin1Ms, // base time - 1ms; kept since newest before lastKeptTime
 				pool3BaseSecBaseMs, // base time; kept since at lastKeptTime
 
-				pool5Min2SBaseMs,   // base time - 2s; deleted
-				pool5Min1SBaseMs,   // base time - 1s; ; deleted
-				pool5BaseSecBaseMs, // base time; kept since at lastKeptTime
-				pool5Plus1SBaseMs,  // base time + 1s; kept since older than lastKeptTime
+				pool5Min2SBaseMsAB, pool5Min2SBaseMsAC, pool5Min2SBaseMsBC, // base time - 2s; deleted
+				pool5Min1SBaseMsAB, pool5Min1SBaseMsAC, pool5Min1SBaseMsBC, // base time - 1s; ; deleted
+				pool5BaseSecBaseMsAB, pool5BaseSecBaseMsAC, pool5BaseSecBaseMsBC, // base time; kept since at lastKeptTime
+				pool5Plus1SBaseMsAB, pool5Plus1SBaseMsAC, pool5Plus1SBaseMsBC, // base time + 1s; kept since older than lastKeptTime
 
-				pool5Min2SMin1Ms,   // base time - 2s - 1ms; deleted
-				pool5Min1SMin1Ms,   // base time - 1s - 1ms; deleted
-				pool5BaseSecMin1Ms, // base time - 1ms; kept since newest before lastKeptTime
-				pool5Plus1SMin1Ms,  // base time + 1s - 1ms; kept since older than lastKeptTime
+				pool5Min2SMin1MsAB, pool5Min2SMin1MsAC, pool5Min2SMin1MsBC, // base time - 2s - 1ms; deleted
+				pool5Min1SMin1MsAB, pool5Min1SMin1MsAC, pool5Min1SMin1MsBC, // base time - 1s - 1ms; deleted
+				pool5BaseSecMin1MsAB, pool5BaseSecMin1MsAC, pool5BaseSecMin1MsBC, // base time - 1ms; kept since newest before lastKeptTime
+				pool5Plus1SMin1MsAB, pool5Plus1SMin1MsAC, pool5Plus1SMin1MsBC, // base time + 1s - 1ms; kept since older than lastKeptTime
 			},
 
 			lastKeptTime: baseTime,
 
 			expectedKeptRecords: []types.TwapRecord{
 				pool3BaseSecMin1Ms,
-				pool5BaseSecMin1Ms,
+				pool5BaseSecMin1MsAB, pool5BaseSecMin1MsAC, pool5BaseSecMin1MsBC,
 				pool3BaseSecBaseMs,
-				pool5BaseSecBaseMs,
-				pool5Plus1SMin1Ms,
-				pool5Plus1SBaseMs,
+				pool5BaseSecBaseMsAB, pool5BaseSecBaseMsAC, pool5BaseSecBaseMsBC,
+				pool5Plus1SMin1MsAB, pool5Plus1SMin1MsAC, pool5Plus1SMin1MsBC,
+				pool5Plus1SBaseMsAB, pool5Plus1SBaseMsAC, pool5Plus1SBaseMsBC,
 			},
 		},
 		"base time - 1s - 2 ms; all pools; all test records": {
@@ -363,10 +379,10 @@ func (s *TestSuite) TestPruneRecordsBeforeTimeButNewest() {
 				pool3BaseSecMin1Ms, // base time - 1ms; kept since older
 				pool3BaseSecBaseMs, // base time; kept since older
 
-				pool2Min1SMin3Ms, // base time - 1s - 3ms; kept since newest before lastKeptTime
-				pool2Min1SMin2Ms, // base time - 1s - 2ms; kept since at lastKeptTime
-				pool2Min1SMin1Ms, // base time - 1s - 1ms; kept since older
-				pool2Min1SBaseMs, // base time - 1s; kept since older
+				pool2Min1SMin3MsAB, pool2Min1SMin3MsAC, pool2Min1SMin3MsBC, // base time - 1s - 3ms; kept since newest before lastKeptTime
+				pool2Min1SMin2MsAB, pool2Min1SMin2MsAC, pool2Min1SMin2MsBC, // base time - 1s - 2ms; kept since at lastKeptTime
+				pool2Min1SMin1MsAB, pool2Min1SMin1MsAC, pool2Min1SMin1MsBC, // base time - 1s - 1ms; kept since older
+				pool2Min1SBaseMsAB, pool2Min1SBaseMsAC, pool2Min1SBaseMsBC, // base time - 1s; kept since older
 
 				pool1Min2SMin3Ms, // base time - 2s - 3ms; deleted
 				pool1Min2SMin2Ms, // base time - 2s - 2ms; deleted
@@ -378,58 +394,41 @@ func (s *TestSuite) TestPruneRecordsBeforeTimeButNewest() {
 				pool4Plus1SMin1Ms, // base time + 1s -1ms; kept since older
 				pool4Plus1SBaseMs, // base time + 1s; kept since older
 
-				pool5Min2SBaseMs,   // base time - 2s; kept since newest before lastKeptTime
-				pool5Min1SBaseMs,   // base time - 1s; kept since older
-				pool5BaseSecBaseMs, // base time; kept since older
-				pool5Plus1SBaseMs,  // base time + 1s; kept since older
+				pool5Min2SBaseMsAB, pool5Min2SBaseMsAC, pool5Min2SBaseMsBC, // base time - 2s; kept since newest before lastKeptTime
+				pool5Min1SBaseMsAB, pool5Min1SBaseMsAC, pool5Min1SBaseMsBC, // base time - 1s; kept since older
+				pool5BaseSecBaseMsAB, pool5BaseSecBaseMsAC, pool5BaseSecBaseMsBC, // base time; kept since older
+				pool5Plus1SBaseMsAB, pool5Plus1SBaseMsAC, pool5Plus1SBaseMsBC, // base time + 1s; kept since older
 
-				pool5Min2SMin1Ms,   // base time - 2s - 1ms; deleted
-				pool5Min1SMin1Ms,   // base time - 1s - 1ms; kept since older
-				pool5BaseSecMin1Ms, // base time - 1ms; kept since older
-				pool5Plus1SMin1Ms,  // base time + 1s - 1ms; kept since older
+				pool5Min2SMin1MsAB, pool5Min2SMin1MsAC, pool5Min2SMin1MsBC, // base time - 2s - 1ms; deleted
+				pool5Min1SMin1MsAB, pool5Min1SMin1MsAC, pool5Min1SMin1MsBC, // base time - 1s - 1ms; kept since older
+				pool5BaseSecMin1MsAB, pool5BaseSecMin1MsAC, pool5BaseSecMin1MsBC, // base time - 1ms; kept since older
+				pool5Plus1SMin1MsAB, pool5Plus1SMin1MsAC, pool5Plus1SMin1MsBC, // base time + 1s - 1ms; kept since older
 			},
 
 			lastKeptTime: baseTime.Add(-time.Second).Add(2 * -time.Millisecond),
 
 			expectedKeptRecords: []types.TwapRecord{
-				pool1Min2SBaseMs,   // base time - 2s; kept since newest before lastKeptTime
-				pool5Min2SBaseMs,   // base time - 2s; kept since newest before lastKeptTime
-				pool2Min1SMin3Ms,   // base time - 1s - 3ms; kept since newest before lastKeptTime
-				pool2Min1SMin2Ms,   // base time - 1s - 2ms; kept since at lastKeptTime
-				pool2Min1SMin1Ms,   // base time - 1s - 1ms; kept since older
-				pool5Min1SMin1Ms,   // base time - 1s - 1ms; kept since older
-				pool2Min1SBaseMs,   // base time - 1s; kept since older
-				pool5Min1SBaseMs,   // base time - 1s; kept since older
-				pool3BaseSecMin3Ms, // base time - 3ms; kept since older
-				pool3BaseSecMin2Ms, // base time - 2ms; kept since older
-				pool3BaseSecMin1Ms, // base time - 1ms; kept since older
-				pool5BaseSecMin1Ms, // base time - 1ms; kept since older
-				pool3BaseSecBaseMs, // base time; kept since older
-				pool5BaseSecBaseMs, // base time; kept since older
-				pool4Plus1SMin3Ms,  // base time + 1s - 3ms; kept since older
-				pool4Plus1SMin2Ms,  // base time + 1s - 2ms; kept since older
-				pool4Plus1SMin1Ms,  // base time + 1s -1ms; kept since older
-				pool5Plus1SMin1Ms,  // base time + 1s - 1ms; kept since older
-				pool4Plus1SBaseMs,  // base time + 1s; kept since older
-				pool5Plus1SBaseMs,  // base time + 1s; kept since older
-			},
-		},
-		"multi-asset pool": {
-			recordsToPreSet: []types.TwapRecord{
-				pool3BaseSecMin1Ms,                     // base time - 1ms; kept since newest before lastKeptTime
-				pool3BaseSecBaseMs,                     // base time; kept since at lastKeptTime
-				pool3BaseSecMin3Ms,                     // base time - 3ms; deleted
-				pool3BaseSecMin2Ms,                     // base time - 2ms; deleted
-				withDiffPoolAssets(pool3BaseSecMin1Ms), // base time - 1ms; kept since newest before lastKeptTime
-				withDiffPoolAssets(pool3BaseSecBaseMs), // base time; kept since at lastKeptTime
-				withDiffPoolAssets(pool3BaseSecMin3Ms), // base time - 3ms; deleted
-				withDiffPoolAssets(pool3BaseSecMin2Ms), // base time - 2ms; deleted
+				pool1Min2SBaseMs,                                           // base time - 2s; kept since newest before lastKeptTime
+				pool5Min2SBaseMsAB, pool5Min2SBaseMsAC, pool5Min2SBaseMsBC, // base time - 2s; kept since newest before lastKeptTime
+				pool2Min1SMin3MsAB, pool2Min1SMin3MsAC, pool2Min1SMin3MsBC, // base time - 1s - 3ms; kept since newest before lastKeptTime
+				pool2Min1SMin2MsAB, pool2Min1SMin2MsAC, pool2Min1SMin2MsBC, // base time - 1s - 2ms; kept since at lastKeptTime
+				pool2Min1SMin1MsAB, pool2Min1SMin1MsAC, pool2Min1SMin1MsBC, // base time - 1s - 1ms; kept since older
+				pool5Min1SMin1MsAB, pool5Min1SMin1MsAC, pool5Min1SMin1MsBC, // base time - 1s - 1ms; kept since older
+				pool2Min1SBaseMsAB, pool2Min1SBaseMsAC, pool2Min1SBaseMsBC, // base time - 1s; kept since older
+				pool5Min1SBaseMsAB, pool5Min1SBaseMsAC, pool5Min1SBaseMsBC, // base time - 1s; kept since older
+				pool3BaseSecMin3Ms,                                               // base time - 3ms; kept since older
+				pool3BaseSecMin2Ms,                                               // base time - 2ms; kept since older
+				pool3BaseSecMin1Ms,                                               // base time - 1ms; kept since older
+				pool5BaseSecMin1MsAB, pool5BaseSecMin1MsAC, pool5BaseSecMin1MsBC, // base time - 1ms; kept since older
+				pool3BaseSecBaseMs,                                               // base time; kept since older
+				pool5BaseSecBaseMsAB, pool5BaseSecBaseMsAC, pool5BaseSecBaseMsBC, // base time; kept since older
+				pool4Plus1SMin3Ms,                                             // base time + 1s - 3ms; kept since older
+				pool4Plus1SMin2Ms,                                             // base time + 1s - 2ms; kept since older
+				pool4Plus1SMin1Ms,                                             // base time + 1s -1ms; kept since older
+				pool5Plus1SMin1MsAB, pool5Plus1SMin1MsAC, pool5Plus1SMin1MsBC, // base time + 1s - 1ms; kept since older
+				pool4Plus1SBaseMs,                                             // base time + 1s; kept since older
+				pool5Plus1SBaseMsAB, pool5Plus1SBaseMsAC, pool5Plus1SBaseMsBC, // base time + 1s; kept since older
 			},
-
-			lastKeptTime: baseTime,
-
-			expectedKeptRecords: []types.TwapRecord{withDiffPoolAssets(pool3BaseSecMin1Ms), pool3BaseSecMin1Ms,
-				withDiffPoolAssets(pool3BaseSecBaseMs), pool3BaseSecBaseMs},
 		},
 		"no pre-set records - no error": {
 			recordsToPreSet: []types.TwapRecord{},
diff --git a/x/twap/types/export_test.go b/x/twap/types/export_test.go
index 1cd624068..ed305fc00 100644
--- a/x/twap/types/export_test.go
+++ b/x/twap/types/export_test.go
@@ -43,5 +43,4 @@ func TestValidatePeriod(t *testing.T) {
 			require.NoError(t, err)
 		})
 	}
-
 }
diff --git a/x/twap/types/utils_test.go b/x/twap/types/utils_test.go
index 791fd3b7f..d757faf0e 100644
--- a/x/twap/types/utils_test.go
+++ b/x/twap/types/utils_test.go
@@ -49,8 +49,10 @@ func TestLexicographicalOrderDenoms(t *testing.T) {
 	}{
 		"basic":    {"A", "B", "A", "B", nil},
 		"basicRev": {"B", "A", "A", "B", nil},
-		"realDenoms": {"uosmo", "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
-			"ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", "uosmo", nil},
+		"realDenoms": {
+			"uosmo", "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+			"ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", "uosmo", nil,
+		},
 		"sameDenom": {"A", "A", "", "", fmt.Errorf("both assets cannot be of the same denom: assetA: %s, assetB: %s", "A", "A")},
 	}
 	for name, tt := range tests {