Repository: osmosis-labs/osmosis
Pull request #2063 (2022-07-14 11:25:05)

chore(e2e): use `require.Eventually` in QueryRPC
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
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!
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >  -->

Closes: #2051 

## What is the purpose of the change

Currently we use custom loops with custom logic to try connecting to the rpc enpoints.
This PR includes replacing that custom loop with `require.Eventually`


## Brief Changelog

- Change custom loop used for qurieying to `require.Eventually`


## Testing and Verifying

This change is a trivial rework / code cleanup without any test coverage.

## Documentation and Release Note

  - Does this pull request introduce a new feature or user-facing behavior changes? no
  - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? no
  - How is the feature or change documented? (not applicable   /   specification (`x/<module>/spec/`)  /  [Osmosis docs repo](https://github.com/osmosis-labs/docs)   /   not documented)


Patch:
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 49370713b..f2e92a956 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -9,6 +9,13 @@
   - app/upgrades/**/*
   # Also match any module.go update
   - x/**/module.go
+"C:simulator":
+  # Match all of simulation & tests/simulator folder
+  - simulation/**/*
+  - simulation/**/**/*
+  - tests/simulator/**
+  # Match any module update
+  - x/**/simulation/*.go
 "C:wasm":
   - app/wasm/**/*
 "C:x/claim":
diff --git a/.github/workflows/sim.yml b/.github/workflows/sim.yml
index c57ce3871..cf2b59c99 100644
--- a/.github/workflows/sim.yml
+++ b/.github/workflows/sim.yml
@@ -18,4 +18,4 @@ jobs:
         run: go version
       - name: Run simulation
         run: |
-          # make test-sim
+          make test-sim
diff --git a/.golangci.yml b/.golangci.yml
index 7c9d3d831..60b55e94d 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -111,5 +111,8 @@ issues:
     - linters:
       - stylecheck
       text: "ST1003:" # requires identifiers with "id" to be "ID".
+    - linters:
+      - stylecheck
+      text: "ST1005:" # punctuation in error messages
   max-issues-per-linter: 10000
   max-same-issues: 10000
diff --git a/Makefile b/Makefile
index 766e6f39f..6eb060d17 100644
--- a/Makefile
+++ b/Makefile
@@ -237,6 +237,9 @@ test-e2e:
 test-e2e-skip-upgrade:
 	@VERSION=$(VERSION) OSMOSIS_E2E_SKIP_UPGRADE=True go test -mod=readonly -timeout=25m -v $(PACKAGES_E2E)
 
+test-mutation:
+	@sh scripts/mutation-test.sh
+
 benchmark:
 	@go test -mod=readonly -bench=. $(PACKAGES_UNIT)
 
@@ -253,6 +256,8 @@ docker-build-e2e-init-chain:
 docker-build-e2e-init-node:
 	@docker build -t osmosis-e2e-init-node:debug --build-arg E2E_SCRIPT_NAME=node -f tests/e2e/initialization/init.Dockerfile .
 
+.PHONY: test-mutation
+
 ###############################################################################
 ###                                Linting                                  ###
 ###############################################################################
diff --git a/go.mod b/go.mod
index 2f7557701..067730b5f 100644
--- a/go.mod
+++ b/go.mod
@@ -24,6 +24,7 @@ require (
 	github.com/stretchr/testify v1.8.0
 	github.com/tendermint/tendermint v0.34.19
 	github.com/tendermint/tm-db v0.6.8-0.20220506192307-f628bb5dc95b
+	github.com/zimmski/go-mutesting v0.0.0-20210610104036-6d9217011a00
 	golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d
 	google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd
 	google.golang.org/grpc v1.46.2
@@ -151,6 +152,7 @@ require (
 	github.com/imdario/mergo v0.3.12 // indirect
 	github.com/improbable-eng/grpc-web v0.14.1 // 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
 	github.com/jingyugao/rowserrcheck v1.1.1 // indirect
 	github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
@@ -246,6 +248,8 @@ require (
 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 	github.com/yagipy/maintidx v1.0.0 // indirect
 	github.com/yeya24/promlinter v0.2.0 // indirect
+	github.com/zimmski/go-tool v0.0.0-20150119110811-2dfdc9ac8439 // indirect
+	github.com/zimmski/osutil v0.0.0-20190128123334-0d0b3ca231ac // indirect
 	github.com/zondax/hid v0.9.0 // indirect
 	gitlab.com/bosi/decorder v0.2.1 // indirect
 	go.etcd.io/bbolt v1.3.6 // indirect
diff --git a/go.sum b/go.sum
index 2b6732e62..976dfb6b8 100644
--- a/go.sum
+++ b/go.sum
@@ -757,6 +757,7 @@ github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod
 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
 github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU=
 github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM=
 github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
@@ -796,6 +797,7 @@ github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSX
 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/errcheck v1.6.0 h1:YTDO4pNy7AUN/021p+JGHycQyYNIyMoenM1YDVK6RlY=
 github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -890,6 +892,7 @@ github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRR
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mattn/goveralls v0.0.3/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@@ -994,6 +997,7 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
@@ -1384,6 +1388,12 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/zimmski/go-mutesting v0.0.0-20210610104036-6d9217011a00 h1:KNiPkpQpqXvq40f8hh/1T7QasLJT/1MuBoOYA2vlxJk=
+github.com/zimmski/go-mutesting v0.0.0-20210610104036-6d9217011a00/go.mod h1:RJt5SMnyha63GbdwCKJiX9djvvEC4KsfXJSZ5oTmSPw=
+github.com/zimmski/go-tool v0.0.0-20150119110811-2dfdc9ac8439 h1:yHqsjUkj0HWbKPw/6ZqC0/eMklaRpqubA199vaRLzzE=
+github.com/zimmski/go-tool v0.0.0-20150119110811-2dfdc9ac8439/go.mod h1:G4FVqCRvfz74AEB1crDNdQuvMfOoKtk7DlePsnV2yGs=
+github.com/zimmski/osutil v0.0.0-20190128123334-0d0b3ca231ac h1:uiFRlKzyIzHeLOthe0ethUkSGW7POlqxU3Tc21R8QpQ=
+github.com/zimmski/osutil v0.0.0-20190128123334-0d0b3ca231ac/go.mod h1:wJ9WGevuM/rw8aB2pQPFMUgXZWeaouI0ueFamR0DUPE=
 github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8=
 github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
 gitlab.com/bosi/decorder v0.2.1 h1:ehqZe8hI4w7O4b1vgsDZw1YU1PE7iJXrQWFMsocbQ1w=
@@ -1759,6 +1769,7 @@ golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190307163923-6a08e3108db3/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4=
@@ -1783,6 +1794,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191018212557-ed542cd5b28a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
diff --git a/osmomath/math.go b/osmomath/math.go
index def0f5014..d8f341bb6 100644
--- a/osmomath/math.go
+++ b/osmomath/math.go
@@ -84,6 +84,10 @@ func Pow(base sdk.Dec, exp sdk.Dec) sdk.Dec {
 // Contract: 0 < base <= 2
 // 0 <= exp < 1.
 func PowApprox(base sdk.Dec, exp sdk.Dec, precision sdk.Dec) sdk.Dec {
+	if !base.IsPositive() {
+		panic(fmt.Errorf("base must be greater than 0"))
+	}
+
 	if exp.IsZero() {
 		return sdk.OneDec()
 	}
diff --git a/simulation/executor/event_stats.go b/simulation/executor/event_stats.go
index 0965ed348..8b3f06e9b 100644
--- a/simulation/executor/event_stats.go
+++ b/simulation/executor/event_stats.go
@@ -48,7 +48,7 @@ func (es EventStats) ExportJSON(path string) {
 		panic(err)
 	}
 
-	err = ioutil.WriteFile(path, bz, 0600)
+	err = ioutil.WriteFile(path, bz, 0o600)
 	if err != nil {
 		panic(err)
 	}
@@ -56,9 +56,10 @@ func (es EventStats) ExportJSON(path string) {
 
 func (es EventStats) exportEvents(ExportStatsPath string, w io.Writer) {
 	if ExportStatsPath != "" {
-		fmt.Println("Exporting simulation statistics...")
+		fmt.Printf("Exporting simulation statistics to %s...", ExportStatsPath)
 		es.ExportJSON(ExportStatsPath)
 	} else {
+		// TODO: Don't print, also export to a file of timestamp'd path
 		es.Print(w)
 	}
 }
diff --git a/simulation/executor/log.go b/simulation/executor/log.go
index df563cc18..d429497a4 100644
--- a/simulation/executor/log.go
+++ b/simulation/executor/log.go
@@ -44,7 +44,6 @@ func (lw *StandardLogWriter) PrintLogs() {
 	for i := 0; i < len(lw.OpEntries); i++ {
 		writeEntry := fmt.Sprintf("%s\n", (lw.OpEntries[i]).MustMarshal())
 		_, err := f.WriteString(writeEntry)
-
 		if err != nil {
 			panic("Failed to write logs to file")
 		}
diff --git a/simulation/executor/mock_tendermint.go b/simulation/executor/mock_tendermint.go
index e4f096db1..db9f1bfae 100644
--- a/simulation/executor/mock_tendermint.go
+++ b/simulation/executor/mock_tendermint.go
@@ -126,7 +126,8 @@ func updateValidators(
 func RandomRequestBeginBlock(r *rand.Rand, params Params,
 	validators mockValidators, pastTimes []time.Time,
 	pastVoteInfos [][]abci.VoteInfo,
-	event func(route, op, evResult string), header tmproto.Header) abci.RequestBeginBlock {
+	event func(route, op, evResult string), header tmproto.Header,
+) abci.RequestBeginBlock {
 	if len(validators) == 0 {
 		return abci.RequestBeginBlock{
 			Header: header,
diff --git a/simulation/executor/operation.go b/simulation/executor/operation.go
index 38a018a61..76387ed2b 100644
--- a/simulation/executor/operation.go
+++ b/simulation/executor/operation.go
@@ -108,6 +108,7 @@ func queueOperations(queuedOps OperationQueue, queuedTimeOps []simulation.Future
 		queuedTimeOps[index] = futureOp
 	}
 }
+
 func totalWeight(actions []simtypes.Action) int {
 	totalWeight := 0
 	for _, action := range actions {
diff --git a/simulation/executor/params.go b/simulation/executor/params.go
index 44c903951..ccf226334 100644
--- a/simulation/executor/params.go
+++ b/simulation/executor/params.go
@@ -81,7 +81,7 @@ func (p Params) BlockSizeTransitionMatrix() simulation.TransitionMatrix {
 func RandomParams(r *rand.Rand) Params {
 	return Params{
 		pastEvidenceFraction:      r.Float64(),
-		numKeys:                   simulation.RandIntBetween(r, 2, 2500), // number of accounts created for the simulation
+		numKeys:                   simulation.RandIntBetween(r, 50, 2500), // number of accounts created for the simulation
 		evidenceFraction:          r.Float64() / 2,
 		initialLivenessWeightings: []int{simulation.RandIntBetween(r, 1, 80), r.Intn(10), r.Intn(10)},
 		livenessTransitionMatrix:  defaultLivenessTransitionMatrix,
diff --git a/simulation/executor/simulate.go b/simulation/executor/simulate.go
index 4fd61d25f..5cda014a2 100644
--- a/simulation/executor/simulate.go
+++ b/simulation/executor/simulate.go
@@ -174,6 +174,7 @@ func SimulateFromSeed(
 			"\nSimulation complete; Final height (blocks): %d, final time (seconds): %v, operations ran: %d\n",
 			simState.header.Height, simState.header.Time, simState.opCount,
 		)
+		simState.logWriter.PrintLogs()
 	}
 
 	simState.eventStats.exportEvents(config.ExportStatsPath, w)
@@ -204,7 +205,8 @@ type blockSimFn func(simCtx *simtypes.SimCtx, ctx sdk.Context, header tmproto.He
 // Returns a function to simulate blocks. Written like this to avoid constant
 // parameters being passed everytime, to minimize memory overhead.
 func createBlockSimulator(testingMode bool, w io.Writer, params Params, actions []simtypes.Action,
-	simState *simState, config simulation.Config) blockSimFn {
+	simState *simState, config simulation.Config,
+) blockSimFn {
 	lastBlockSizeState := 0 // state for [4 * uniform distribution]
 	blocksize := 0
 	selectAction := getSelectActionFn(actions)
@@ -294,7 +296,8 @@ func (simState *simState) runQueuedOperations(simCtx *simtypes.SimCtx, ctx sdk.C
 }
 
 func (simState *simState) runQueuedTimeOperations(simCtx *simtypes.SimCtx, ctx sdk.Context) (
-	numOpsRan int) {
+	numOpsRan int,
+) {
 	queueOps := simState.timeOperationQueue
 	currentTime := simState.header.Time
 	numOpsRan = 0
diff --git a/simulation/executor/util.go b/simulation/executor/util.go
index 2ecbbbd3e..e21849d4d 100644
--- a/simulation/executor/util.go
+++ b/simulation/executor/util.go
@@ -9,7 +9,6 @@ import (
 	"github.com/cosmos/cosmos-sdk/baseapp"
 	"github.com/cosmos/cosmos-sdk/client"
 	"github.com/cosmos/cosmos-sdk/codec"
-	"github.com/cosmos/cosmos-sdk/simapp/helpers"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	simulation "github.com/cosmos/cosmos-sdk/types/simulation"
 
@@ -80,50 +79,3 @@ type OperationInput struct {
 	Bankkeeper      BankKeeper
 	ModuleName      string
 }
-
-// GenAndDeliverTxWithRandFees generates a transaction with a random fee and delivers it.
-func GenAndDeliverTxWithRandFees(txCtx OperationInput) (simulation.OperationMsg, []simulation.FutureOperation, error) {
-	account := txCtx.AccountKeeper.GetAccount(txCtx.Context, txCtx.SimAccount.Address)
-	spendable := txCtx.Bankkeeper.SpendableCoins(txCtx.Context, account.GetAddress())
-
-	var fees sdk.Coins
-	var err error
-
-	coins, hasNeg := spendable.SafeSub(txCtx.CoinsSpentInMsg)
-	if hasNeg {
-		return simulation.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "message doesn't leave room for fees"), nil, err
-	}
-
-	spendableFeeCoins := coins.FilterDenoms([]string{sdk.DefaultBondDenom})
-	fees, err = simulation.RandomFees(txCtx.R, txCtx.Context, spendableFeeCoins)
-	if err != nil {
-		return simulation.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate fees"), nil, err
-	}
-	return GenAndDeliverTx(txCtx, fees)
-}
-
-// GenAndDeliverTx generates a transactions and delivers it.
-func GenAndDeliverTx(txCtx OperationInput, fees sdk.Coins) (simulation.OperationMsg, []simulation.FutureOperation, error) {
-	account := txCtx.AccountKeeper.GetAccount(txCtx.Context, txCtx.SimAccount.Address)
-	tx, err := helpers.GenTx(
-		txCtx.TxGen,
-		[]sdk.Msg{txCtx.Msg},
-		fees,
-		helpers.DefaultGenTxGas,
-		txCtx.Context.ChainID(),
-		[]uint64{account.GetAccountNumber()},
-		[]uint64{account.GetSequence()},
-		txCtx.SimAccount.PrivKey,
-	)
-
-	if err != nil {
-		return simulation.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate mock tx"), nil, err
-	}
-
-	_, _, err = txCtx.App.Deliver(txCtx.TxGen.TxEncoder(), tx)
-	if err != nil {
-		return simulation.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to deliver tx"), nil, err
-	}
-
-	return simulation.NewOperationMsg(txCtx.Msg, true, "", txCtx.Cdc), nil, nil
-}
diff --git a/simulation/types/account.go b/simulation/types/account.go
index 7b2f504d9..dcde70d08 100644
--- a/simulation/types/account.go
+++ b/simulation/types/account.go
@@ -1,6 +1,7 @@
 package simulation
 
 import (
+	"errors"
 	"fmt"
 	"math"
 
@@ -62,6 +63,17 @@ func (sim *SimCtx) FindAccount(address sdk.Address) (simulation.Account, bool) {
 	return simulation.Account{}, false
 }
 
+func (sim *SimCtx) RandomSimAccountWithBalance(ctx sdk.Context) (simulation.Account, error) {
+	accHasBal := func(acc simulation.Account) bool {
+		return len(sim.App.GetBankKeeper().SpendableCoins(ctx, acc.Address)) != 0
+	}
+	acc, found := sim.RandomSimAccountWithConstraint(accHasBal)
+	if !found {
+		return simulation.Account{}, errors.New("no address with balance found. Check simulator configuration, this should be very rare.")
+	}
+	return acc, nil
+}
+
 // Returns (account, randSubsetCoins, found), so if found = false, then no such address exists.
 // randSubsetCoins is a random subset of the provided denoms, if the account is found.
 // TODO: Write unit test
@@ -86,8 +98,12 @@ func (sim *SimCtx) SelAddrWithDenoms(ctx sdk.Context, denoms []string) (simulati
 // RandGeometricCoin uniformly samples a denom from the addr's balances.
 // Then it samples an Exponentially distributed amount of the addr's coins, with rate = 10.
 // (Meaning that on average it samples 10% of the chosen balance)
+// Pre-condition: Addr must have a spendable balance
 func (sim *SimCtx) RandExponentialCoin(ctx sdk.Context, addr sdk.AccAddress) sdk.Coin {
-	balances := sim.App.GetBankKeeper().GetAllBalances(ctx, addr)
+	balances := sim.App.GetBankKeeper().SpendableCoins(ctx, addr)
+	if len(balances) == 0 {
+		panic("precondition for RandExponentialCoin broken: Addr has 0 spendable balance")
+	}
 	coin := RandSelect(sim, balances...)
 	// TODO: Reconsider if this becomes problematic in the future, but currently thinking it
 	// should be fine for simulation.
diff --git a/simulation/types/action.go b/simulation/types/action.go
index 64eaac5a9..4cdfa130b 100644
--- a/simulation/types/action.go
+++ b/simulation/types/action.go
@@ -34,7 +34,8 @@ type weightedOperationAction struct {
 func (a weightedOperationAction) Name() string   { return "weighted_op" }
 func (a weightedOperationAction) Weight() Weight { return Weight(a.op.Weight()) }
 func (a weightedOperationAction) Execute(sim *SimCtx, ctx sdk.Context) (
-	simulation.OperationMsg, []simulation.FutureOperation, error) {
+	simulation.OperationMsg, []simulation.FutureOperation, error,
+) {
 	return a.op.Op()(sim.GetRand(), sim.App.GetBaseApp(), ctx, sim.Accounts, sim.ChainID)
 }
 
@@ -85,7 +86,8 @@ func (m msgBasedAction) WithWeight(weight Weight) msgBasedAction {
 func (m msgBasedAction) Name() string   { return m.name }
 func (m msgBasedAction) Weight() Weight { return m.weight }
 func (m msgBasedAction) Execute(sim *SimCtx, ctx sdk.Context) (
-	OperationMsg simulation.OperationMsg, futureOps []simulation.FutureOperation, err error) {
+	OperationMsg simulation.OperationMsg, futureOps []simulation.FutureOperation, err error,
+) {
 	msg, err := m.msgGenerator(sim, ctx)
 	if err != nil {
 		return simulation.NoOpMsg(m.name, m.name, fmt.Sprintf("unable to build msg due to: %v", err)), nil, nil
diff --git a/simulation/types/manager.go b/simulation/types/manager.go
index 1f0270d35..063f693b6 100644
--- a/simulation/types/manager.go
+++ b/simulation/types/manager.go
@@ -10,6 +10,8 @@ import (
 	"github.com/cosmos/cosmos-sdk/types/module"
 	"github.com/cosmos/cosmos-sdk/types/simulation"
 	"golang.org/x/exp/maps"
+
+	"github.com/osmosis-labs/osmosis/v7/osmoutils"
 )
 
 // AppModuleSimulation defines the standard functions that every module should expose
@@ -110,8 +112,10 @@ func (m Manager) legacyActions(seed int64, cdc codec.JSONCodec) []Action {
 // TODO: Can we use sim here instead? Perhaps by passing in the simulation module manager to the simulator.
 func (m Manager) Actions(seed int64, cdc codec.JSONCodec) []Action {
 	actions := m.legacyActions(seed, cdc)
-	for _, simModule := range m.Modules {
-		actions = append(actions, simModule.Actions()...)
+	moduleKeys := maps.Keys(m.Modules)
+	osmoutils.SortSlice(moduleKeys)
+	for _, simModuleName := range moduleKeys {
+		actions = append(actions, m.Modules[simModuleName].Actions()...)
 	}
 	return actions
 }
diff --git a/simulation/types/random/sdkrand.go b/simulation/types/random/sdkrand.go
index a2ab78b86..89c950ef3 100644
--- a/simulation/types/random/sdkrand.go
+++ b/simulation/types/random/sdkrand.go
@@ -53,7 +53,7 @@ func RandPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) {
 // RandomAmount generates a random amount
 // Note: The range of RandomAmount includes max, and is, in fact, biased to return max as well as 0.
 func RandomAmount(r *rand.Rand, max sdk.Int) sdk.Int {
-	var randInt = big.NewInt(0)
+	randInt := big.NewInt(0)
 
 	switch r.Intn(10) {
 	case 0:
@@ -70,7 +70,7 @@ func RandomAmount(r *rand.Rand, max sdk.Int) sdk.Int {
 // RandomDecAmount generates a random decimal amount
 // Note: The range of RandomDecAmount includes max, and is, in fact, biased to return max as well as 0.
 func RandomDecAmount(r *rand.Rand, max sdk.Dec) sdk.Dec {
-	var randInt = big.NewInt(0)
+	randInt := big.NewInt(0)
 
 	switch r.Intn(10) {
 	case 0:
diff --git a/simulation/types/txbuilder.go b/simulation/types/txbuilder.go
index 87d0719c0..05c9c47fb 100644
--- a/simulation/types/txbuilder.go
+++ b/simulation/types/txbuilder.go
@@ -4,11 +4,17 @@ import (
 	"errors"
 	"fmt"
 
+	"github.com/cosmos/cosmos-sdk/client"
+	"github.com/cosmos/cosmos-sdk/client/tx"
 	"github.com/cosmos/cosmos-sdk/simapp/helpers"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/cosmos/cosmos-sdk/types/simulation"
+	"github.com/cosmos/cosmos-sdk/types/tx/signing"
 
 	"github.com/osmosis-labs/osmosis/v7/app/params"
+
+	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
+	authsign "github.com/cosmos/cosmos-sdk/x/auth/signing"
 )
 
 //nolint:deadcode,unused
@@ -31,7 +37,7 @@ func (sim *SimCtx) defaultTxBuilder(
 	// TODO: Consider making a default tx builder that charges some random fees
 	// Low value for amount of work right now though.
 	fees := sdk.Coins{}
-	tx, err := helpers.GenTx(
+	tx, err := genTx(
 		txConfig,
 		[]sdk.Msg{msg},
 		fees,
@@ -57,3 +63,58 @@ func (sim *SimCtx) deliverTx(tx sdk.Tx, msg sdk.Msg, msgName string) (simulation
 
 	return simulation.NewOperationMsg(msg, true, "", nil), nil, nil
 }
+
+// GenTx generates a signed mock transaction.
+// TODO: Surely theres proper API's in the SDK for this?
+// (This was copied from SDK simapp, and deleted the egregiously non-deterministic memo handling)
+func genTx(gen client.TxConfig, msgs []sdk.Msg, feeAmt sdk.Coins, gas uint64, chainID string, accNums, accSeqs []uint64, priv ...cryptotypes.PrivKey) (sdk.Tx, error) {
+	sigs := make([]signing.SignatureV2, len(priv))
+	memo := "sample_memo"
+	signMode := gen.SignModeHandler().DefaultMode()
+
+	// 1st round: set SignatureV2 with empty signatures, to set correct
+	// signer infos.
+	for i, p := range priv {
+		sigs[i] = signing.SignatureV2{
+			PubKey: p.PubKey(),
+			Data: &signing.SingleSignatureData{
+				SignMode: signMode,
+			},
+			Sequence: accSeqs[i],
+		}
+	}
+
+	txBuilder := gen.NewTxBuilder()
+	err := txBuilder.SetMsgs(msgs...)
+	if err != nil {
+		return nil, err
+	}
+	err = txBuilder.SetSignatures(sigs...)
+	if err != nil {
+		return nil, err
+	}
+	txBuilder.SetMemo(memo)
+	txBuilder.SetFeeAmount(feeAmt)
+	txBuilder.SetGasLimit(gas)
+
+	// 2nd round: once all signer infos are set, every signer can sign.
+	for i, p := range priv {
+		signerData := authsign.SignerData{
+			ChainID:       chainID,
+			AccountNumber: accNums[i],
+			Sequence:      accSeqs[i],
+		}
+		sig, err := tx.SignWithPrivKey(signMode, signerData, txBuilder, p, gen, accSeqs[i])
+		if err != nil {
+			panic(err)
+		}
+		//nolint:forcetypeassert
+		sigs[i] = sig
+		err = txBuilder.SetSignatures(sigs...)
+		if err != nil {
+			panic(err)
+		}
+	}
+
+	return txBuilder.GetTx(), nil
+}
diff --git a/tools/tools.go b/tools/tools.go
index a16035f61..61580ec66 100644
--- a/tools/tools.go
+++ b/tools/tools.go
@@ -9,5 +9,6 @@ package tools
 
 import (
 	_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
+	_ "github.com/zimmski/go-mutesting/cmd/go-mutesting"
 	_ "mvdan.cc/gofumpt"
 )
diff --git a/x/gamm/keeper/internal/events/emit.go b/x/gamm/keeper/internal/events/emit.go
new file mode 100644
index 000000000..a9d68682f
--- /dev/null
+++ b/x/gamm/keeper/internal/events/emit.go
@@ -0,0 +1,70 @@
+package events
+
+import (
+	"strconv"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/osmosis-labs/osmosis/v7/x/gamm/types"
+)
+
+func EmitSwapEvent(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, input sdk.Coins, output sdk.Coins) {
+	if ctx.EventManager() == nil {
+		return
+	}
+
+	ctx.EventManager().EmitEvents(sdk.Events{
+		newSwapEvent(sender, poolId, input, output),
+	})
+}
+
+func newSwapEvent(sender sdk.AccAddress, poolId uint64, input sdk.Coins, output sdk.Coins) sdk.Event {
+	return sdk.NewEvent(
+		types.TypeEvtTokenSwapped,
+		sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
+		sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
+		sdk.NewAttribute(types.AttributeKeyPoolId, strconv.FormatUint(poolId, 10)),
+		sdk.NewAttribute(types.AttributeKeyTokensIn, input.String()),
+		sdk.NewAttribute(types.AttributeKeyTokensOut, output.String()),
+	)
+}
+
+func EmitAddLiquidityEvent(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, liquidity sdk.Coins) {
+	if ctx.EventManager() == nil {
+		return
+	}
+
+	ctx.EventManager().EmitEvents(sdk.Events{
+		newAddLiquidityEvent(sender, poolId, liquidity),
+	})
+}
+
+func newAddLiquidityEvent(sender sdk.AccAddress, poolId uint64, liquidity sdk.Coins) sdk.Event {
+	return sdk.NewEvent(
+		types.TypeEvtPoolJoined,
+		sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
+		sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
+		sdk.NewAttribute(types.AttributeKeyPoolId, strconv.FormatUint(poolId, 10)),
+		sdk.NewAttribute(types.AttributeKeyTokensIn, liquidity.String()),
+	)
+}
+
+func EmitRemoveLiquidityEvent(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, liquidity sdk.Coins) {
+	if ctx.EventManager() == nil {
+		return
+	}
+
+	ctx.EventManager().EmitEvents(sdk.Events{
+		newRemoveLiquidityEvent(sender, poolId, liquidity),
+	})
+}
+
+func newRemoveLiquidityEvent(sender sdk.AccAddress, poolId uint64, liquidity sdk.Coins) sdk.Event {
+	return sdk.NewEvent(
+		types.TypeEvtPoolExited,
+		sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
+		sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
+		sdk.NewAttribute(types.AttributeKeyPoolId, strconv.FormatUint(poolId, 10)),
+		sdk.NewAttribute(types.AttributeKeyTokensOut, liquidity.String()),
+	)
+}
diff --git a/x/gamm/keeper/keeper.go b/x/gamm/keeper/keeper.go
index 97d937d07..6e557a524 100644
--- a/x/gamm/keeper/keeper.go
+++ b/x/gamm/keeper/keeper.go
@@ -2,7 +2,6 @@ package keeper
 
 import (
 	"fmt"
-	"strconv"
 
 	"github.com/osmosis-labs/osmosis/v7/x/gamm/types"
 
@@ -61,46 +60,6 @@ func NewKeeper(cdc codec.BinaryCodec, storeKey sdk.StoreKey, paramSpace paramtyp
 	}
 }
 
-// nolint: unused
-func (k *Keeper) createSwapEvent(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, input sdk.Coins, output sdk.Coins) {
-	ctx.EventManager().EmitEvents(sdk.Events{
-		sdk.NewEvent(
-			types.TypeEvtTokenSwapped,
-			sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
-			sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
-			sdk.NewAttribute(types.AttributeKeyPoolId, strconv.FormatUint(poolId, 10)),
-			sdk.NewAttribute(types.AttributeKeyTokensIn, input.String()),
-			sdk.NewAttribute(types.AttributeKeyTokensOut, output.String()),
-		),
-	})
-}
-
-// nolint: unused
-func (k *Keeper) createAddLiquidityEvent(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, liquidity sdk.Coins) {
-	ctx.EventManager().EmitEvents(sdk.Events{
-		sdk.NewEvent(
-			types.TypeEvtPoolJoined,
-			sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
-			sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
-			sdk.NewAttribute(types.AttributeKeyPoolId, strconv.FormatUint(poolId, 10)),
-			sdk.NewAttribute(types.AttributeKeyTokensIn, liquidity.String()),
-		),
-	})
-}
-
-// nolint: unused
-func (k *Keeper) createRemoveLiquidityEvent(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, liquidity sdk.Coins) {
-	ctx.EventManager().EmitEvents(sdk.Events{
-		sdk.NewEvent(
-			types.TypeEvtPoolExited,
-			sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
-			sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
-			sdk.NewAttribute(types.AttributeKeyPoolId, strconv.FormatUint(poolId, 10)),
-			sdk.NewAttribute(types.AttributeKeyTokensOut, liquidity.String()),
-		),
-	})
-}
-
 // Set the gamm hooks.
 func (k *Keeper) SetHooks(gh types.GammHooks) *Keeper {
 	if k.hooks != nil {
diff --git a/x/gamm/keeper/multihop.go b/x/gamm/keeper/multihop.go
index 852cdd9cb..1e80fb17c 100644
--- a/x/gamm/keeper/multihop.go
+++ b/x/gamm/keeper/multihop.go
@@ -52,6 +52,9 @@ func (k Keeper) MultihopSwapExactAmountOut(
 	if err != nil {
 		return sdk.Int{}, err
 	}
+	if len(insExpected) == 0 {
+		return sdk.Int{}, nil
+	}
 
 	insExpected[0] = tokenInMaxAmount
 
diff --git a/x/gamm/keeper/share.go b/x/gamm/keeper/share.go
index ae76041b0..7423c264b 100644
--- a/x/gamm/keeper/share.go
+++ b/x/gamm/keeper/share.go
@@ -3,6 +3,7 @@ package keeper
 import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
+	"github.com/osmosis-labs/osmosis/v7/x/gamm/keeper/internal/events"
 	"github.com/osmosis-labs/osmosis/v7/x/gamm/types"
 )
 
@@ -22,7 +23,7 @@ func (k Keeper) applyJoinPoolStateChange(ctx sdk.Context, pool types.PoolI, join
 		return err
 	}
 
-	ctx.EventManager().EmitEvent(types.CreateAddLiquidityEvent(ctx, joiner, pool.GetId(), joinCoins))
+	events.EmitAddLiquidityEvent(ctx, joiner, pool.GetId(), joinCoins)
 	k.hooks.AfterJoinPool(ctx, joiner, pool.GetId(), joinCoins, numShares)
 	k.RecordTotalLiquidityIncrease(ctx, joinCoins)
 	return nil
@@ -44,7 +45,7 @@ func (k Keeper) applyExitPoolStateChange(ctx sdk.Context, pool types.PoolI, exit
 		return err
 	}
 
-	ctx.EventManager().EmitEvent(types.CreateRemoveLiquidityEvent(ctx, exiter, pool.GetId(), exitCoins))
+	events.EmitRemoveLiquidityEvent(ctx, exiter, pool.GetId(), exitCoins)
 	k.hooks.AfterExitPool(ctx, exiter, pool.GetId(), numShares, exitCoins)
 	k.RecordTotalLiquidityDecrease(ctx, exitCoins)
 	return nil
diff --git a/x/gamm/keeper/swap.go b/x/gamm/keeper/swap.go
index ec3f6f71a..a9fa3ee65 100644
--- a/x/gamm/keeper/swap.go
+++ b/x/gamm/keeper/swap.go
@@ -6,6 +6,7 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
 
+	"github.com/osmosis-labs/osmosis/v7/x/gamm/keeper/internal/events"
 	"github.com/osmosis-labs/osmosis/v7/x/gamm/types"
 )
 
@@ -166,7 +167,7 @@ func (k Keeper) updatePoolForSwap(
 		return err
 	}
 
-	ctx.EventManager().EmitEvent(types.CreateSwapEvent(ctx, sender, pool.GetId(), tokensIn, tokensOut))
+	events.EmitSwapEvent(ctx, sender, pool.GetId(), tokensIn, tokensOut)
 	k.hooks.AfterSwap(ctx, sender, pool.GetId(), tokensIn, tokensOut)
 	k.RecordTotalLiquidityIncrease(ctx, tokensIn)
 	k.RecordTotalLiquidityDecrease(ctx, tokensOut)
diff --git a/x/gamm/spec/README.md b/x/gamm/spec/README.md
index 5189919cc..a986d7f35 100644
--- a/x/gamm/spec/README.md
+++ b/x/gamm/spec/README.md
@@ -147,13 +147,9 @@ The GAMM module also has a **PoolCreationFee** parameter, which currently is set
 
 [comment]: <> (TODO Add better description of how the weights affect things)
 
-
-
 </br>
 </br>
 
-
-
 ## Messages
 
 The `x/gamm` module supports the following message types:
@@ -196,7 +192,6 @@ The `x/gamm` module supports the following message types:
 
 ## Transactions
 
-
 ### Create pool
 
 Create a new liquidity pool and provide initial liquidity to it.
@@ -235,13 +230,13 @@ The configuration json file contains the following parameters:
  "future-governor": ""
 }
 ```
+
 :::
 
 ::: warning
 There is now a 100 OSMO fee for creating pools.
 :::
 
-
 ### Join pool
 
 Add liquidity to a specified pool to get an **exact** amount of LP shares while specifying a **maximum** number tokens willing to swap to receive said LP shares.
@@ -257,9 +252,8 @@ Join `pool 3` with a **maximum** of `.037753 AKT` and the corresponding amount o
 ```sh
 osmosisd tx gamm join-pool --pool-id 3 --max-amounts-in 37753ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 --share-amount-out 1227549469722224220 --from WALLET_NAME --chain-id osmosis-1
 ```
-:::
-
 
+:::
 
 ### Exit pool
 
@@ -276,9 +270,8 @@ Exit `pool 3` with for **exactly** `1.136326462628731195 gamm/pool/3` in order t
 ```sh
 osmosisd tx gamm exit-pool --pool-id 3 --min-amounts-out 33358ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 --share-amount-in 1136326462628731195 --from WALLET_NAME --chain-id osmosis-1
 ```
-:::
-
 
+:::
 
 ### Join-swap-extern-amount-in
 
@@ -297,10 +290,8 @@ Join `pool 3` with **exactly** `.200000 AKT` (and `0 OSMO`) to get a **minimum**
 ```sh
 osmosisd tx gamm join-swap-extern-amount-in 200000ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 3234812471272883046 --pool-id 3 --from WALLET_NAME --chain-id osmosis-1
 ```
-:::
-
-
 
+:::
 
 ### Exit-swap-extern-amount-out
 
@@ -319,10 +310,8 @@ Exit `pool 3` by removing a **maximum** of `3.408979387886193586 gamm/pool/3` an
 ```sh
 osmosisd tx gamm exit-swap-extern-amount-out 199430ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 3408979387886193586 --pool-id 3 --from WALLET_NAME --chain-id osmosis-1
 ```
-:::
-
-
 
+:::
 
 ### Join-swap-share-amount-out
 
@@ -339,10 +328,8 @@ Swap a **maximum** of `0.312466 OSMO` for the corresponding amount of `AKT`, the
 ```sh
 osmosisd tx gamm join-swap-share-amount-out uosmo 312466 14481270389710236872 --pool-id 3 --from WALLET_NAME --chain-id osmosis-1
 ```
-:::
-
-
 
+:::
 
 ### Exit-swap-share-amount-in
 
@@ -359,14 +346,13 @@ Exit `pool 3` by removing **exactly** `14.563185400026723131 gamm/pool/3` and sw
 ```sh
 osmosisd tx gamm exit-swap-share-amount-in uosmo 14563185400026723131 298548 --pool-id 3 --from WALLET_NAME --chain-id osmosis-1
 ```
-:::
 
+:::
 
 ### Swap-exact-amount-in
 
 Swap an **exact** amount of tokens for a **minimum** of another token, similar to swapping a token on the trade screen GUI.
 
-
 ```sh
 osmosisd tx gamm swap-exact-amount-in [token-in] [token-out-min-amount] --pool-id --from --chain-id
 ```
@@ -378,9 +364,8 @@ Swap **exactly** `.407239 AKT` through `pool 3` into a **minimum** of `.140530 O
 ```sh
 osmosisd tx gamm swap-exact-amount-in 407239ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 140530 --swap-route-pool-ids 3 --swap-route-denoms uosmo --from WALLET_NAME --chain-id osmosis-1
 ```
-:::
-
 
+:::
 
 ### Swap-exact-amount-out
 
@@ -398,15 +383,11 @@ Swap a **maximum** of `.407239 AKT` through `pool 3` into **exactly** `.140530 O
 osmosisd tx gamm swap-exact-amount-out 140530uosmo 407239 --swap-route-pool-ids 3 --swap-route-denoms ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 --from WALLET_NAME --chain-id osmosis-1
 ```
 
-
 [comment]: <> (Other resources Creating a liquidity bootstrapping pool and Creating a pool with a pool file)
 :::
 
-
-
 ## Queries and Transactions
 
-
 ## Queries
 
 The **Query** submodule of the GAMM module provides the logic to request information from the liquidity pools. It contains the following functions:
@@ -423,26 +404,35 @@ The **Query** submodule of the GAMM module provides the logic to request informa
 - [Total Share](#total-share)
 
 ### Estimate Swap Exact Amount In
+
 Query the estimated result of the [Swap Exact Amount In](#swap-exact-amount-in) transaction. Note that the flags *swap-route-pool* and *swap-route-denoms* are required.
+
 #### Usage
+
 ```sh
 osmosisd query gamm estimate-swap-exact-amount-in <poolID> <sender> <tokenIn> [flags]
 ```
+
 #### Example
+
 Query the amount of ATOM the sender would receive for swapping 1 OSMO in pool 1.
 
 ```sh
 osmosisd query gamm estimate-swap-exact-amount-in 1 osmo123nfq6m8f88m4g3sky570unsnk4zng4uqv7cm8 1000000uosmo --swap-route-pool-ids 1 --swap-route-denoms ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 
 ```
 
-
 ### Estimate Swap Exact Amount Out
+
 Query the estimated result of the [Swap Exact Amount Out](#swap-exact-amount-out) transaction. Note that the flags *swap-route-pool* and *swap-route-denoms* are required.
+
 #### Usage
+
 ```sh
 osmosisd query gamm estimate-swap-exact-amount-out <poolID> <sender> <tokenOut> [flags]
 ```
+
 #### Example
+
 Query the amount of OSMO the sender would require to swap 1 ATOM out of pool 1.
 
 ```sh
@@ -450,14 +440,17 @@ osmosisd query gamm estimate-swap-exact-amount-out 1 osmo123nfq6m8f88m4g3sky570u
 ```
 
 ### Num Pools
+
 Query the number of active pools.
 
 #### Usage
+
 ```sh
 osmosisd query gamm num-pools
 ```
 
 ## Pool
+
 Query the parameter and assets of a specific pool.
 
 ### Usage
@@ -474,10 +467,12 @@ Query parameters and assets from pool 1.
 osmosisd query gamm pool 1
 ```
 
-
 ### Pool Assets
+
 Query the assets of a specific pool. This query is a reduced form of the [Pool](#pool) query.
+
 #### Usage
+
 ```sh
 osmosisd query gamm pool-assets <poolID> [flags]
 ```
@@ -490,56 +485,64 @@ Query the assets from pool 1.
 osmosisd query gamm pool-assets 1
 ```
 
-
 ### Pool Params
+
 Query the parameters of a specific pool. This query is a reduced form of the [Pool](#pool) query.
+
 #### Usage
+
 ```sh
 osmosisd query gamm pool-params <poolID> [flags]
 ```
 
 Query the parameters from pool 1.
+
 #### Example
+
 ```sh
 osmosisd query gamm pool-params 1
 ```
 
-
 ### Pools
-Query parameters and assets of all active pools.
 
+Query parameters and assets of all active pools.
 
 #### Usage
+
 Query the price of OSMO based on the price of ATOM in pool 1.
 
 ```sh
 osmosisd query gamm spot-price 1 uosmo ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2
 ```
 
-
 ### Total Liquidity
+
 Query the total liquidity of all active pools.
+
 #### Usage
+
 ```sh
 osmosisd query gamm total-liquidity
 ```
 
-
 ### Total Share
+
 Query the total amount of GAMM shares of a specific pool.
+
 #### Usage
+
 ```sh
 osmosisd query gamm total-share <poolID> [flags]
 ```
+
 #### Example
+
 Query the total amount of GAMM shares of pool 1.
 
 ```sh
 osmosisd query gamm total-share 1
 ```
 
-
-
 ## Transactions
 
 The **Transaction** submodule of the GAMM module provides the logic to create and interact with the liquidity pools. It contains the following functions:
@@ -569,9 +572,8 @@ Query the amount of ATOM (or `ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA
 ```sh
 osmosisd query gamm estimate-swap-exact-amount-in 1 osmo123nfq6m8f88m4g3sky570unsnk4zng4uqv7cm8 1000000uosmo --swap-route-pool-ids 1 --swap-route-denoms ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2
 ```
-:::
-
 
+:::
 
 ### Estimate-swap-exact-amount-out
 
@@ -588,9 +590,8 @@ Query the amount of `OSMO` the `sender` would require to swap 1 ATOM (or `100000
 ```sh
 osmosisd query gamm estimate-swap-exact-amount-out 1 osmo123nfq6m8f88m4g3sky570unsnk4zng4uqv7cm8 1000000ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 --swap-route-pool-ids 1 --swap-route-denoms uosmo
 ```
-:::
-
 
+:::
 
 ### Num-pools
 
@@ -600,8 +601,6 @@ Query the number of active pools.
 osmosisd query gamm num-pools
 ```
 
-
-
 ### Pool
 
 Query the parameter and assets of a specific pool.
@@ -644,12 +643,11 @@ Which outputs:
       amount: "21388879300450"
     weight: "500000.000000000000000000"
 ```
-:::
 
+:::
 
 ### Pool-assets
 
-
 Query the assets of a specific pool. This query is a reduced form of the [pool](#pool) query.
 
 ```sh
@@ -677,8 +675,8 @@ poolAssets:
     denom: uosmo
   weight: "536870912000000"
 ```
-:::
 
+:::
 
 ### Pool Params
 
@@ -703,26 +701,19 @@ swap_fee: "0.003000000000000000"
 exit_fee: "0.000000000000000000"
 smooth_weight_change_params: null
 ```
-:::
-
 
+:::
 
 ### Pools
 
-
 Query parameters and assets of all active pools.
 
 ```sh
 osmosisd query gamm pools
 ```
 
-
-
-
-
 ### Spot-price
 
-
 Query the spot price of a pool asset based on a specific pool it is in.
 
 ```sh
@@ -746,26 +737,16 @@ spotPrice: "5.314387014412388547"
 In other words, at the time of this writing, ~5.314 OSMO is equivalent to 1 ATOM.
 :::
 
-
-
-
-
 ### Total-liquidity
 
-
 Query the total liquidity of all active pools.
 
 ```sh
 osmosisd query gamm total-liquidity
 ```
 
-
-
-
-
 ### Total-share
 
-
 Query the total amount of GAMM shares of a specific pool.
 
 ```sh
@@ -792,9 +773,8 @@ Indicating there are a total of `252328895.834096787303097071 gamm/pool/1` at th
 
 :::
 
-
-
 ### Create Pool
+
 Create a new liquidity pool and provide the initial liquidity to it. Pool initialization parameters must be provided through a JSON file using the flag *pool-file*.
 
 #### Usage
@@ -814,7 +794,9 @@ The configuration file *config.json* must specify the following parameters.
  "future-governor": [number of hours]
 }
 ```
+
 #### Example
+
 Create a new ATOM-OSMO liquidity pool with a swap and exit fee of 1%.
 
 ```sh
@@ -833,9 +815,8 @@ The configuration file contains the following parameters.
 }
 ```
 
-
-
 ### Join Pool
+
 Join a specific pool with a custom amount of tokens. Note that the flags *pool-id*, *max-amounts-in* and *share-amount-out* are required.
 
 #### Usage
@@ -852,8 +833,8 @@ Join pool 1 with 1 OSMO and the respective amount of ATOM, using myKeyringWallet
 osmosisd tx gamm join-pool --pool-id 2 --max-amounts-in 1000000uosmo --max-amounts-in 1000000uion --share-amount-out 1000000 --from myKeyringWallet
 ```
 
-
 ### Exit Pool
+
 Exit a specific pool with a custom amount of tokens. Note that the flags *pool-id*, *min-amounts-out* and *share-amount-in* are required.
 
 #### Usage
@@ -870,8 +851,8 @@ Exit pool one with 1 OSMO and the respective amount of ATOM using myKeyringWalle
 osmosisd tx gamm exit-pool --pool-id 1 --min-amounts-out 1000000uosmo --share-amount-in 1000000 --from myKeyringWallet
 ```
 
-
 ### Join Swap Extern Amount In
+
 Note that the flags *pool-id* is required.
 
 #### Usage
@@ -886,8 +867,8 @@ osmosisd tx gamm join-swap-extern-amount-in [token-in] [share-out-min-amount] [f
 osmosisd tx gamm join-swap-extern-amount-in 1000000uosmo 1000000 --pool-id 1 --from myKeyringWallet
 ```
 
-
 ### Exit Swap Extern Amount Out
+
 Note that the flag *pool-id* is required.
 
 ```sh
@@ -900,8 +881,8 @@ osmosisd tx gamm exit-swap-extern-amount-out [token-out] [share-in-max-amount] [
 osmosisd tx gamm exit-swap-extern-amount-out 1000000uosmo 1000000 --pool-id 1 --from myKeyringWallet
 ```
 
-
 ### Join Swap Share Amount Out
+
 Note that the flag *pool-id* is required.
 
 #### Usage
@@ -916,8 +897,8 @@ osmosisd tx gamm join-swap-share-amount-out [token-in-denom] [token-in-max-amoun
 osmosisd tx gamm join-swap-share-amount-out uosmo 1000000 1000000 --pool-id 1 --from myKeyringWallet
 ```
 
-
 ### Exit Swap Share Amount In
+
 Note that the flag *pool-id* is required.
 
 #### Usage
@@ -933,6 +914,7 @@ osmosisd tx gamm exit-swap-share-amount-in uosmo 1000000 1000000 --pool-id 1 --f
 ```
 
 ### Swap Exact Amount In
+
 Swap an exact amount of tokens into a specific pool. Note that the flags *swap-route-pool-ids* and *swap-route-denoms* are required.
 
 #### Usage
@@ -949,8 +931,8 @@ Swap 1 OSMO through pool 1 into at least 0.3 ATOM using MyKeyringWallet.
 osmosisd tx gamm swap-exact-amount-in 1000000uosmo 300000 --swap-route-pool-ids 1 --swap-route-denoms ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 --from MyKeyringWallet
 ```
 
-
 ### Swap Exact Amount Out
+
 Swap an exact amount of tokens out of a specific pool. Note that the flags *swap-route-pool-ids* and *swap-route-denoms* are required.
 
 ### Usage
@@ -968,6 +950,48 @@ osmosisd tx gamm swap-exact-amount-out 1000000ibc/27394FB092D2ECCD56123C74F36E4C
 ```
 
 ## Other resources
+
 * [Creating a liquidity bootstrapping pool](./client/docs/create-lbp-pool.md)
 * [Creating a pool with a pool file](./client/docs/create-pool.md)
 
+## Events
+
+There are 4 types of events that exist in GAMM:
+
+* `sdk.EventTypeMessage` - "message"
+* `types.TypeEvtPoolJoined` - "pool_joined"
+* `types.TypeEvtPoolExited` - "pool_exited"
+* `types.TypeEvtPoolCreated` - "pool_created"
+* `types.TypeEvtTokenSwapped` - "token_swapped"
+
+### `sdk.EventTypeMessage`
+
+This event is emitted in the message server when any of the gamm messages finish execution.
+
+TBD
+
+### `types.TypeEvtPoolExited`
+
+TBD
+
+### `types.TypeEvtPoolCreated`
+
+TBD
+
+### `types.TypeEvtTokenSwapped`
+
+This event is emitted after one of `SwapExactAmountOut` or `SwapExactAmountIn` updates
+pool state successfully.
+
+It consists of the following attributes:
+
+* `sdk.AttributeKeyModule` - "module"
+  * The value is the module's name - "gamm".
+* sdk.AttributeKeySender
+  * The value is the address of the sender who created the swap message.
+* types.AttributeKeyPoolId
+  * The value is the pool id of the pool where swap occurs.
+* types.AttributeKeyTokensIn
+  * The value is the string representation of the tokens being swapped in.
+* types.AttributeKeyTokensOut
+  * The value is the string representation of the tokens being swapped out.
diff --git a/x/gamm/types/events.go b/x/gamm/types/events.go
index e65c07f42..baed080cf 100644
--- a/x/gamm/types/events.go
+++ b/x/gamm/types/events.go
@@ -1,11 +1,5 @@
 package types
 
-import (
-	"strconv"
-
-	sdk "github.com/cosmos/cosmos-sdk/types"
-)
-
 const (
 	TypeEvtPoolJoined   = "pool_joined"
 	TypeEvtPoolExited   = "pool_exited"
@@ -18,34 +12,3 @@ const (
 	AttributeKeyTokensIn   = "tokens_in"
 	AttributeKeyTokensOut  = "tokens_out"
 )
-
-func CreateSwapEvent(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, input sdk.Coins, output sdk.Coins) sdk.Event {
-	return sdk.NewEvent(
-		TypeEvtTokenSwapped,
-		sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory),
-		sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
-		sdk.NewAttribute(AttributeKeyPoolId, strconv.FormatUint(poolId, 10)),
-		sdk.NewAttribute(AttributeKeyTokensIn, input.String()),
-		sdk.NewAttribute(AttributeKeyTokensOut, output.String()),
-	)
-}
-
-func CreateAddLiquidityEvent(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, liquidity sdk.Coins) sdk.Event {
-	return sdk.NewEvent(
-		TypeEvtPoolJoined,
-		sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory),
-		sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
-		sdk.NewAttribute(AttributeKeyPoolId, strconv.FormatUint(poolId, 10)),
-		sdk.NewAttribute(AttributeKeyTokensIn, liquidity.String()),
-	)
-}
-
-func CreateRemoveLiquidityEvent(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, liquidity sdk.Coins) sdk.Event {
-	return sdk.NewEvent(
-		TypeEvtPoolExited,
-		sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory),
-		sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
-		sdk.NewAttribute(AttributeKeyPoolId, strconv.FormatUint(poolId, 10)),
-		sdk.NewAttribute(AttributeKeyTokensOut, liquidity.String()),
-	)
-}
diff --git a/x/lockup/simulation/operations.go b/x/lockup/simulation/operations.go
index a0ab4d528..7056a595a 100644
--- a/x/lockup/simulation/operations.go
+++ b/x/lockup/simulation/operations.go
@@ -15,13 +15,16 @@ import (
 )
 
 func RandomMsgLockTokens(k keeper.Keeper, sim *simulation.SimCtx, ctx sdk.Context) (*types.MsgLockTokens, error) {
-	sender := sim.RandomSimAccount()
-	lockCoins := sim.RandExponentialCoin(ctx, sender.Address)
+	sender, err := sim.RandomSimAccountWithBalance(ctx)
+	if err != nil {
+		return nil, err
+	}
+	lockCoin := sim.RandExponentialCoin(ctx, sender.Address)
 	duration := simulation.RandSelect(sim, time.Minute, time.Hour, time.Hour*24)
 	return &types.MsgLockTokens{
 		Owner:    sender.Address.String(),
 		Duration: duration,
-		Coins:    sdk.Coins{lockCoins},
+		Coins:    sdk.Coins{lockCoin},
 	}, nil
 }
 
diff --git a/x/mint/keeper/keeper.go b/x/mint/keeper/keeper.go
index dbca5cdb5..f010155c8 100644
--- a/x/mint/keeper/keeper.go
+++ b/x/mint/keeper/keeper.go
@@ -33,7 +33,7 @@ type invalidRatioError struct {
 }
 
 func (e invalidRatioError) Error() string {
-	return fmt.Sprintf("mint allocation ratio %s is greater than 1", e.ActualRatio)
+	return fmt.Sprintf("mint allocation ratio (%s) is greater than 1", e.ActualRatio)
 }
 
 var (
@@ -198,98 +198,126 @@ func (k Keeper) DistributeMintedCoin(ctx sdk.Context, mintedCoin sdk.Coin) error
 	params := k.GetParams(ctx)
 	proportions := params.DistributionProportions
 
-	// allocate staking incentives into fee collector account to be moved to on next begin blocker by staking module
-	stakingIncentivesCoin, err := k.distributeToModule(ctx, k.feeCollectorName, mintedCoin, proportions.Staking)
+	// allocate staking incentives into fee collector account to be moved to on next begin blocker by staking module account.
+	stakingIncentivesAmount, err := k.distributeToModule(ctx, k.feeCollectorName, mintedCoin, proportions.Staking)
 	if err != nil {
 		return err
 	}
 
-	// allocate pool allocation ratio to pool-incentives module account account
-	poolIncentivesCoin, err := k.distributeToModule(ctx, poolincentivestypes.ModuleName, mintedCoin, proportions.PoolIncentives)
+	// allocate pool allocation ratio to pool-incentives module account.
+	poolIncentivesAmount, err := k.distributeToModule(ctx, poolincentivestypes.ModuleName, mintedCoin, proportions.PoolIncentives)
 	if err != nil {
 		return err
 	}
 
-	devRewardCoin, err := getProportions(ctx, mintedCoin, proportions.DeveloperRewards)
+	// allocate dev rewards to respective accounts from developer vesting module account.
+	devRewardAmount, err := k.distributeDeveloperRewards(ctx, mintedCoin, proportions.DeveloperRewards, params.WeightedDeveloperRewardsReceivers)
 	if err != nil {
 		return err
 	}
+
+	// subtract from original provision to ensure no coins left over after the allocations
+	communityPoolAmount := mintedCoin.Amount.Sub(stakingIncentivesAmount).Sub(poolIncentivesAmount).Sub(devRewardAmount)
+	err = k.distrKeeper.FundCommunityPool(ctx, sdk.NewCoins(sdk.NewCoin(params.MintDenom, communityPoolAmount)), k.accountKeeper.GetModuleAddress(types.ModuleName))
+	if err != nil {
+		return err
+	}
+
+	// call an hook after the minting and distribution of new coins
+	k.hooks.AfterDistributeMintedCoin(ctx, mintedCoin)
+
+	return err
+}
+
+// distributeToModule distributes mintedCoin multiplied by proportion to the recepientModule account.
+func (k Keeper) distributeToModule(ctx sdk.Context, recipientModule string, mintedCoin sdk.Coin, proportion sdk.Dec) (sdk.Int, error) {
+	distributionCoin, err := getProportions(ctx, mintedCoin, proportion)
+	if err != nil {
+		return sdk.Int{}, err
+	}
+	if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, recipientModule, sdk.NewCoins(distributionCoin)); err != nil {
+		return sdk.Int{}, err
+	}
+	return distributionCoin.Amount, nil
+}
+
+// distributeDeveloperRewards distributes developer rewards from developer vesting module account
+// to the respective account receivers by weight (developerRewardsReceivers).
+// If no developer reward receivers given, funds the community pool instead.
+// Returns the total amount distributed from the developer vesting module account.
+// Updates supply offsets to reflect the amount of coins distributed. This is done so because the developer rewards distributions are
+// allocated from its own module account, not the mint module accont (TODO: next step in https://github.com/osmosis-labs/osmosis/issues/1916).
+// Returns nil on success, error otherwise.
+// With respect to input parameters, errors occur when:
+// - developerRewardsProportion is greater than 1.
+// - invalid address in developer rewards receivers.
+// - the balance of developer module account is less than totalMintedCoin * developerRewardsProportion.
+// - the balance of mint module is less than totalMintedCoin * developerRewardsProportion.
+// CONTRACT:
+// - weights in developerRewardsReceivers add up to 1.
+// - addresses in developerRewardsReceivers are valid.
+func (k Keeper) distributeDeveloperRewards(ctx sdk.Context, totalMintedCoin sdk.Coin, developerRewardsProportion sdk.Dec, developerRewardsReceivers []types.WeightedAddress) (sdk.Int, error) {
+	devRewardCoin, err := getProportions(ctx, totalMintedCoin, developerRewardsProportion)
+	if err != nil {
+		return sdk.Int{}, err
+	}
 	devRewardCoins := sdk.NewCoins(devRewardCoin)
 	// This is supposed to come from the developer vesting module address, not the mint module address
 	// we over-allocated to the mint module address earlier though, so we burn it right here.
 	if err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, devRewardCoins); err != nil {
-		return err
+		return sdk.Int{}, err
 	}
 
 	// Take the current balance of the developer rewards pool and remove it from the supply offset
 	// We re-introduce the new supply at the end, in order to avoid any rounding discrepancies.
-	developerAccountBalance := k.bankKeeper.GetBalance(ctx, k.accountKeeper.GetModuleAddress(types.DeveloperVestingModuleAcctName), mintedCoin.Denom)
-	k.bankKeeper.AddSupplyOffset(ctx, mintedCoin.Denom, developerAccountBalance.Amount)
+	developerAccountBalance := k.bankKeeper.GetBalance(ctx, k.accountKeeper.GetModuleAddress(types.DeveloperVestingModuleAcctName), totalMintedCoin.Denom)
+	k.bankKeeper.AddSupplyOffset(ctx, totalMintedCoin.Denom, developerAccountBalance.Amount)
 
-	if len(params.WeightedDeveloperRewardsReceivers) == 0 {
+	if len(developerRewardsReceivers) == 0 {
 		// fund community pool when rewards address is empty
 		if err := k.distrKeeper.FundCommunityPool(ctx, devRewardCoins, k.accountKeeper.GetModuleAddress(types.DeveloperVestingModuleAcctName)); err != nil {
-			return err
+			return sdk.Int{}, err
 		}
 	} else {
 		// allocate developer rewards to addresses by weight
-		for _, w := range params.WeightedDeveloperRewardsReceivers {
+		for _, w := range developerRewardsReceivers {
 			devPortionCoin, err := getProportions(ctx, devRewardCoin, w.Weight)
 			if err != nil {
-				return err
+				return sdk.Int{}, err
 			}
 			devRewardPortionCoins := sdk.NewCoins(devPortionCoin)
 			if w.Address == "" {
 				err := k.distrKeeper.FundCommunityPool(ctx, devRewardPortionCoins,
 					k.accountKeeper.GetModuleAddress(types.DeveloperVestingModuleAcctName))
 				if err != nil {
-					return err
+					return sdk.Int{}, err
 				}
 			} else {
 				devRewardsAddr, err := sdk.AccAddressFromBech32(w.Address)
 				if err != nil {
-					return err
+					return sdk.Int{}, err
 				}
 				// If recipient is vesting account, pay to account according to its vesting condition
 				err = k.bankKeeper.SendCoinsFromModuleToAccount(
 					ctx, types.DeveloperVestingModuleAcctName, devRewardsAddr, devRewardPortionCoins)
 				if err != nil {
-					return err
+					return sdk.Int{}, err
 				}
 			}
 		}
 	}
 
 	// Take the new balance of the developer rewards pool and add it back to the supply offset deduction
-	developerAccountBalance = k.bankKeeper.GetBalance(ctx, k.accountKeeper.GetModuleAddress(types.DeveloperVestingModuleAcctName), mintedCoin.Denom)
-	k.bankKeeper.AddSupplyOffset(ctx, mintedCoin.Denom, developerAccountBalance.Amount.Neg())
-
-	// subtract from original provision to ensure no coins left over after the allocations
-	communityPoolCoin := mintedCoin.Sub(stakingIncentivesCoin).Sub(poolIncentivesCoin).Sub(devRewardCoin)
-	err = k.distrKeeper.FundCommunityPool(ctx, sdk.NewCoins(communityPoolCoin), k.accountKeeper.GetModuleAddress(types.ModuleName))
-	if err != nil {
-		return err
-	}
+	developerAccountBalance = k.bankKeeper.GetBalance(ctx, k.accountKeeper.GetModuleAddress(types.DeveloperVestingModuleAcctName), totalMintedCoin.Denom)
+	k.bankKeeper.AddSupplyOffset(ctx, totalMintedCoin.Denom, developerAccountBalance.Amount.Neg())
 
-	// call an hook after the minting and distribution of new coins
-	k.hooks.AfterDistributeMintedCoin(ctx, mintedCoin)
-
-	return err
-}
-
-func (k Keeper) distributeToModule(ctx sdk.Context, recipientModule string, mintedCoin sdk.Coin, proportion sdk.Dec) (sdk.Coin, error) {
-	distributionCoin, err := getProportions(ctx, mintedCoin, proportion)
-	if err != nil {
-		return sdk.Coin{}, err
-	}
-	if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, recipientModule, sdk.NewCoins(distributionCoin)); err != nil {
-		return sdk.Coin{}, err
-	}
-	return distributionCoin, nil
+	return devRewardCoin.Amount, nil
 }
 
 // getProportions gets the balance of the `MintedDenom` from minted coins and returns coins according to the
 // allocation ratio. Returns error if ratio is greater than 1.
+// TODO: this currently rounds down and is the cause of rounding discrepancies.
+// To be fixed in: https://github.com/osmosis-labs/osmosis/issues/1917
 func getProportions(ctx sdk.Context, mintedCoin sdk.Coin, ratio sdk.Dec) (sdk.Coin, error) {
 	if ratio.GT(sdk.OneDec()) {
 		return sdk.Coin{}, invalidRatioError{ratio}
diff --git a/x/mint/simulation/genesis.go b/x/mint/simulation/genesis.go
index bb121e631..b750d5582 100644
--- a/x/mint/simulation/genesis.go
+++ b/x/mint/simulation/genesis.go
@@ -3,8 +3,6 @@ package simulation
 // DONTCOVER
 
 import (
-	"encoding/json"
-	"fmt"
 	"math/rand"
 
 	"github.com/osmosis-labs/osmosis/v7/x/mint/types"
@@ -140,11 +138,6 @@ func RandomizedGenState(simState *module.SimulationState) {
 
 	mintGenesis := types.NewGenesisState(minter, params, reductionStartedEpoch)
 
-	bz, err := json.MarshalIndent(&mintGenesis, "", " ")
-	if err != nil {
-		panic(err)
-	}
-	fmt.Printf("Selected pseudo-randomly generated minting parameters:\n%s\n", bz)
 	simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(mintGenesis)
 }
 


Test patch:
diff --git a/osmomath/math_test.go b/osmomath/math_test.go
index c01caf80e..b369db336 100644
--- a/osmomath/math_test.go
+++ b/osmomath/math_test.go
@@ -1,10 +1,13 @@
 package osmomath
 
 import (
+	"fmt"
 	"testing"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
+	"github.com/osmosis-labs/osmosis/v7/osmoutils"
+
 	"github.com/stretchr/testify/require"
 )
 
@@ -23,50 +26,143 @@ func TestAbsDifferenceWithSign(t *testing.T) {
 }
 
 func TestPowApprox(t *testing.T) {
-	base, err := sdk.NewDecFromStr("0.8")
-	require.NoError(t, err)
-	exp, err := sdk.NewDecFromStr("0.32")
-	require.NoError(t, err)
-
-	s := PowApprox(base, exp, powPrecision)
-	expectedDec, err := sdk.NewDecFromStr("0.93108385")
-	require.NoError(t, err)
-
-	require.True(
-		t,
-		expectedDec.Sub(s).Abs().LTE(powPrecision),
-		"expected value & actual value's difference should less than precision",
-	)
+	testCases := []struct {
+		base           sdk.Dec
+		exp            sdk.Dec
+		powPrecision   sdk.Dec
+		expectedResult sdk.Dec
+	}{
+		{
+			// medium base, small exp
+			base:           sdk.MustNewDecFromStr("0.8"),
+			exp:            sdk.MustNewDecFromStr("0.32"),
+			powPrecision:   sdk.MustNewDecFromStr("0.00000001"),
+			expectedResult: sdk.MustNewDecFromStr("0.93108385"),
+		},
+		{
+			// zero exp
+			base:           sdk.MustNewDecFromStr("0.8"),
+			exp:            sdk.ZeroDec(),
+			powPrecision:   sdk.MustNewDecFromStr("0.00001"),
+			expectedResult: sdk.OneDec(),
+		},
+		{
+			// zero base, this should panic
+			base:           sdk.ZeroDec(),
+			exp:            sdk.OneDec(),
+			powPrecision:   sdk.MustNewDecFromStr("0.00001"),
+			expectedResult: sdk.ZeroDec(),
+		},
+		{
+			// large base, small exp
+			base:           sdk.MustNewDecFromStr("1.9999"),
+			exp:            sdk.MustNewDecFromStr("0.23"),
+			powPrecision:   sdk.MustNewDecFromStr("0.000000001"),
+			expectedResult: sdk.MustNewDecFromStr("1.172821461"),
+		},
+		{
+			// large base, large integer exp
+			base:           sdk.MustNewDecFromStr("1.777"),
+			exp:            sdk.MustNewDecFromStr("20"),
+			powPrecision:   sdk.MustNewDecFromStr("0.000000000001"),
+			expectedResult: sdk.MustNewDecFromStr("98570.862372081602"),
+		},
+		{
+			// medium base, large exp, high precision
+			base:           sdk.MustNewDecFromStr("1.556"),
+			exp:            sdk.MustNewDecFromStr("20.9123"),
+			powPrecision:   sdk.MustNewDecFromStr("0.0000000000000001"),
+			expectedResult: sdk.MustNewDecFromStr("10360.058421529811344618"),
+		},
+		{
+			// high base, large exp, high precision
+			base:           sdk.MustNewDecFromStr("1.886"),
+			exp:            sdk.MustNewDecFromStr("31.9123"),
+			powPrecision:   sdk.MustNewDecFromStr("0.00000000000001"),
+			expectedResult: sdk.MustNewDecFromStr("621110716.84727942280335811"),
+		},
+		{
+			// base equal one
+			base:           sdk.MustNewDecFromStr("1"),
+			exp:            sdk.MustNewDecFromStr("123"),
+			powPrecision:   sdk.MustNewDecFromStr("0.00000001"),
+			expectedResult: sdk.OneDec(),
+		},
+	}
 
-	base, err = sdk.NewDecFromStr("0.8")
-	require.NoError(t, err)
-	exp = sdk.ZeroDec()
-	require.NoError(t, err)
-
-	s = PowApprox(base, exp, powPrecision)
-	expectedDec = sdk.OneDec()
-	require.NoError(t, err)
-
-	require.True(
-		t,
-		expectedDec.Sub(s).Abs().LTE(powPrecision),
-		"expected value & actual value's difference should less than precision",
-	)
+	for i, tc := range testCases {
+		var actualResult sdk.Dec
+		osmoutils.ConditionalPanic(t, tc.base.Equal(sdk.ZeroDec()), func() {
+			fmt.Println(tc.base)
+			actualResult = PowApprox(tc.base, tc.exp, tc.powPrecision)
+			require.True(
+				t,
+				tc.expectedResult.Sub(actualResult).Abs().LTE(tc.powPrecision),
+				fmt.Sprintf("test %d failed: expected value & actual value's difference should be less than precision", i),
+			)
+		})
+	}
 }
 
 func TestPow(t *testing.T) {
-	base, err := sdk.NewDecFromStr("1.68")
-	require.NoError(t, err)
-	exp, err := sdk.NewDecFromStr("0.32")
-	require.NoError(t, err)
-
-	s := Pow(base, exp)
-	expectedDec, err := sdk.NewDecFromStr("1.18058965")
-	require.NoError(t, err)
+	testCases := []struct {
+		base           sdk.Dec
+		exp            sdk.Dec
+		expectedResult sdk.Dec
+	}{
+		{
+			// medium base, small exp
+			base:           sdk.MustNewDecFromStr("0.8"),
+			exp:            sdk.MustNewDecFromStr("0.32"),
+			expectedResult: sdk.MustNewDecFromStr("0.93108385"),
+		},
+		{
+			// zero exp
+			base:           sdk.MustNewDecFromStr("0.8"),
+			exp:            sdk.ZeroDec(),
+			expectedResult: sdk.OneDec(),
+		},
+		{
+			// zero base, this should panic
+			base:           sdk.ZeroDec(),
+			exp:            sdk.OneDec(),
+			expectedResult: sdk.ZeroDec(),
+		},
+		{
+			// large base, small exp
+			base:           sdk.MustNewDecFromStr("1.9999"),
+			exp:            sdk.MustNewDecFromStr("0.23"),
+			expectedResult: sdk.MustNewDecFromStr("1.172821461"),
+		},
+		{
+			// small base, large exp
+			base:           sdk.MustNewDecFromStr("0.0000123"),
+			exp:            sdk.MustNewDecFromStr("123"),
+			expectedResult: sdk.ZeroDec(),
+		},
+		{
+			// large base, large exp
+			base:           sdk.MustNewDecFromStr("1.777"),
+			exp:            sdk.MustNewDecFromStr("20"),
+			expectedResult: sdk.MustNewDecFromStr("98570.862372081602"),
+		},
+		{
+			// base equal one
+			base:           sdk.MustNewDecFromStr("1"),
+			exp:            sdk.MustNewDecFromStr("123"),
+			expectedResult: sdk.OneDec(),
+		},
+	}
 
-	require.True(
-		t,
-		expectedDec.Sub(s).Abs().LTE(powPrecision),
-		"expected value & actual value's difference should less than precision",
-	)
+	for i, tc := range testCases {
+		var actualResult sdk.Dec
+		osmoutils.ConditionalPanic(t, tc.base.Equal(sdk.ZeroDec()), func() {
+			actualResult = Pow(tc.base, tc.exp)
+			require.True(
+				t,
+				tc.expectedResult.Sub(actualResult).Abs().LTE(powPrecision),
+				fmt.Sprintf("test %d failed: expected value & actual value's difference should be less than precision", i),
+			)
+		})
+	}
 }
diff --git a/scripts/mutation-test.sh b/scripts/mutation-test.sh
new file mode 100644
index 000000000..350aa0d9c
--- /dev/null
+++ b/scripts/mutation-test.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+
+set -eo pipefail
+
+DISABLED_MUTATORS='branch/*'
+
+# Only consider the following:
+# * go files in types or keeper packages
+# * ignore test and Protobuf files
+MUTATION_SOURCES=$(find ./x -type f \( -path '*/keeper/*' -or -path '*/types/*' \) \( -name '*.go' -and -not -name '*_test.go' -and -not -name '*pb*' \))
+
+# XXX: Filter on a module-by-module basis and expand when we think other modules
+# are ready. Once all modules are considered stable enough to be tested, remove
+# this filter entirely.
+MUTATION_SOURCES=$(echo "$MUTATION_SOURCES" | grep './x/tokenfactory')
+
+# Collect multiple lines into a single line to be fed into go-mutesting
+MUTATION_SOURCES=$(echo $MUTATION_SOURCES | tr '\n' ' ')
+
+OUTPUT=$(go run github.com/zimmski/go-mutesting/cmd/go-mutesting --disable=$DISABLED_MUTATORS $MUTATION_SOURCES)
+
+# Fetch the final result output and the overall mutation testing score
+RESULT=$(echo "$OUTPUT" | grep 'The mutation score')
+SCORE=$(echo "$RESULT" | grep -Eo '[[:digit:]]\.[[:digit:]]+')
+
+echo $RESULT
+
+# Return a non-zero exit code if the score is below 75%
+if (( $(echo "$SCORE < 0.75" |bc -l) )); then
+  echo "Mutation testing score below desired level ($SCORE < 0.75)"
+  exit 1
+fi
diff --git a/simulation/executor/params_test.go b/simulation/executor/params_test.go
index 3eb3ad8bc..88cc5d564 100644
--- a/simulation/executor/params_test.go
+++ b/simulation/executor/params_test.go
@@ -43,8 +43,7 @@ func TestNewWeightedProposalContent(t *testing.T) {
 	require.Equal(t, content, pContent.ContentSimulatorFn()(nil, ctx, nil))
 }
 
-type testContent struct {
-}
+type testContent struct{}
 
 func (t testContent) GetTitle() string                { return "" }
 func (t testContent) GetDescription() string          { return "" }
diff --git a/tests/e2e/configurer/chain/queries.go b/tests/e2e/configurer/chain/queries.go
index 3e44415e6..672ecebdb 100644
--- a/tests/e2e/configurer/chain/queries.go
+++ b/tests/e2e/configurer/chain/queries.go
@@ -26,24 +26,15 @@ func (n *NodeConfig) QueryGRPCGateway(path string) ([]byte, error) {
 	fullQueryPath := fmt.Sprintf("%s/%s", endpoint, path)
 
 	var resp *http.Response
-	retriesLeft := 5
-	for {
+	require.Eventually(n.t, func() bool {
 		resp, err = http.Get(fullQueryPath)
-
-		if resp.StatusCode == http.StatusServiceUnavailable {
-			retriesLeft--
-			if retriesLeft == 0 {
-				return nil, err
-			}
-			time.Sleep(10 * time.Second)
-		} else {
-			break
+		if err != nil {
+			n.t.Logf("error while executing HTTP request: %s", err.Error())
+			return false
 		}
-	}
 
-	if err != nil {
-		return nil, fmt.Errorf("failed to execute HTTP request: %w", err)
-	}
+		return resp.StatusCode != http.StatusServiceUnavailable
+	}, time.Minute, time.Second*10, "failed to execute HTTP request")
 
 	defer resp.Body.Close()
 
diff --git a/tests/e2e/configurer/upgrade.go b/tests/e2e/configurer/upgrade.go
index b4c9f73b2..287d665b6 100644
--- a/tests/e2e/configurer/upgrade.go
+++ b/tests/e2e/configurer/upgrade.go
@@ -71,7 +71,6 @@ func (uc *UpgradeConfigurer) ConfigureChain(chainConfig *chain.Config) error {
 	}
 
 	chainInitResource, err := uc.containerManager.RunChainInitResource(chainConfig.Id, int(chainConfig.VotingPeriod), validatorConfigBytes, tmpDir, int(forkHeight))
-
 	if err != nil {
 		return err
 	}
diff --git a/tests/simulator/sim_test.go b/tests/simulator/sim_test.go
index a27fdec46..195e3455b 100644
--- a/tests/simulator/sim_test.go
+++ b/tests/simulator/sim_test.go
@@ -1,7 +1,6 @@
 package simapp
 
 import (
-	"encoding/json"
 	"fmt"
 	"math/rand"
 	"os"
@@ -134,9 +133,9 @@ func TestAppStateDeterminism(t *testing.T) {
 	// }
 
 	config := sdkSimapp.NewConfigFromFlags()
-	config.InitialBlockHeight = 1
 	config.ExportParamsPath = ""
-	config.NumBlocks = 10
+	config.NumBlocks = 50
+	config.BlockSize = 5
 	config.OnOperation = false
 	config.AllInvariants = false
 	config.ChainID = helpers.SimAppChainID
@@ -147,7 +146,7 @@ func TestAppStateDeterminism(t *testing.T) {
 
 	numSeeds := 3
 	numTimesToRunPerSeed := 5
-	appHashList := make([]json.RawMessage, numTimesToRunPerSeed)
+	appHashList := make([]string, numTimesToRunPerSeed)
 
 	for i := 0; i < numSeeds; i++ {
 		config.Seed = rand.Int63()
@@ -199,16 +198,12 @@ func TestAppStateDeterminism(t *testing.T) {
 
 			require.NoError(t, simErr)
 
-			if config.Commit {
-				sdkSimapp.PrintStats(db)
-			}
-
 			appHash := osmosis.LastCommitID().Hash
-			appHashList[j] = appHash
+			appHashList[j] = fmt.Sprintf("%X", appHash)
 
 			if j != 0 {
 				require.Equal(
-					t, string(appHashList[0]), string(appHashList[j]),
+					t, appHashList[0], appHashList[j],
 					"non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed,
 				)
 			}
diff --git a/x/gamm/keeper/internal/events/emit_test.go b/x/gamm/keeper/internal/events/emit_test.go
new file mode 100644
index 000000000..15d60dc5d
--- /dev/null
+++ b/x/gamm/keeper/internal/events/emit_test.go
@@ -0,0 +1,87 @@
+package events_test
+
+import (
+	"strconv"
+	"testing"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/stretchr/testify/suite"
+
+	"github.com/osmosis-labs/osmosis/v7/app/apptesting"
+	"github.com/osmosis-labs/osmosis/v7/x/gamm/keeper/internal/events"
+	"github.com/osmosis-labs/osmosis/v7/x/gamm/types"
+)
+
+type GammEventsTestSuite struct {
+	apptesting.KeeperTestHelper
+}
+
+const (
+	addressString = "addr1---------------"
+	testDenomA    = "denoma"
+	testDenomB    = "denomb"
+	testDenomC    = "denomc"
+	testDenomD    = "denomd"
+)
+
+func TestGammEventsTestSuite(t *testing.T) {
+	suite.Run(t, new(GammEventsTestSuite))
+}
+
+func (suite *GammEventsTestSuite) TestEmitSwapEvent() {
+	testcases := map[string]struct {
+		ctx             sdk.Context
+		testAccountAddr sdk.AccAddress
+		poolId          uint64
+		tokensIn        sdk.Coins
+		tokensOut       sdk.Coins
+	}{
+		"basic valid": {
+			ctx:             suite.CreateTestContext(),
+			testAccountAddr: sdk.AccAddress([]byte(addressString)),
+			poolId:          1,
+			tokensIn:        sdk.NewCoins(sdk.NewCoin(testDenomA, sdk.NewInt(1234))),
+			tokensOut:       sdk.NewCoins(sdk.NewCoin(testDenomB, sdk.NewInt(5678))),
+		},
+		"context with no event manager": {
+			ctx: sdk.Context{},
+		},
+		"valid with multiple tokens in and out": {
+			ctx:             suite.CreateTestContext(),
+			testAccountAddr: sdk.AccAddress([]byte(addressString)),
+			poolId:          200,
+			tokensIn:        sdk.NewCoins(sdk.NewCoin(testDenomA, sdk.NewInt(12)), sdk.NewCoin(testDenomB, sdk.NewInt(99))),
+			tokensOut:       sdk.NewCoins(sdk.NewCoin(testDenomC, sdk.NewInt(88)), sdk.NewCoin(testDenomD, sdk.NewInt(34))),
+		},
+	}
+
+	for name, tc := range testcases {
+		suite.Run(name, func() {
+			expectedEvents := sdk.Events{
+				sdk.NewEvent(
+					types.TypeEvtTokenSwapped,
+					sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
+					sdk.NewAttribute(sdk.AttributeKeySender, tc.testAccountAddr.String()),
+					sdk.NewAttribute(types.AttributeKeyPoolId, strconv.FormatUint(tc.poolId, 10)),
+					sdk.NewAttribute(types.AttributeKeyTokensIn, tc.tokensIn.String()),
+					sdk.NewAttribute(types.AttributeKeyTokensOut, tc.tokensOut.String()),
+				),
+			}
+
+			hasNoEventManager := tc.ctx.EventManager() == nil
+
+			// System under test.
+			events.EmitSwapEvent(tc.ctx, tc.testAccountAddr, tc.poolId, tc.tokensIn, tc.tokensOut)
+
+			// Assertions
+			if hasNoEventManager {
+				// If there is no event manager on context, this is a no-op.
+				return
+			}
+
+			eventManager := tc.ctx.EventManager()
+			actualEvents := eventManager.Events()
+			suite.Equal(expectedEvents, actualEvents)
+		})
+	}
+}
diff --git a/x/gamm/keeper/msg_server_test.go b/x/gamm/keeper/msg_server_test.go
new file mode 100644
index 000000000..a4011404e
--- /dev/null
+++ b/x/gamm/keeper/msg_server_test.go
@@ -0,0 +1,226 @@
+package keeper_test
+
+import (
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/stretchr/testify/suite"
+
+	"github.com/osmosis-labs/osmosis/v7/x/gamm/keeper"
+	"github.com/osmosis-labs/osmosis/v7/x/gamm/types"
+)
+
+const (
+	doesNotExistDenom = "nodenom"
+)
+
+var _ = suite.TestingSuite(nil)
+
+// TestSwapExactAmountIn_Events tests that events are correctly emitted
+// when calling SwapExactAmountIn.
+func (suite *KeeperTestSuite) TestSwapExactAmountIn_Events() {
+	const (
+		tokenInMinAmount = 1
+		tokenIn          = 5
+	)
+
+	testcases := map[string]struct {
+		routes                []types.SwapAmountInRoute
+		tokenIn               sdk.Coin
+		tokenOutMinAmount     sdk.Int
+		expectError           bool
+		expectedSwapEvents    int
+		expectedMessageEvents int
+	}{
+		"zero hops": {
+			routes:                []types.SwapAmountInRoute{},
+			tokenIn:               sdk.NewCoin("foo", sdk.NewInt(tokenIn)),
+			tokenOutMinAmount:     sdk.NewInt(tokenInMinAmount),
+			expectedMessageEvents: 1, // 1 gamm.
+		},
+		"one hop": {
+			routes: []types.SwapAmountInRoute{
+				{
+					PoolId:        1,
+					TokenOutDenom: "bar",
+				},
+			},
+			tokenIn:               sdk.NewCoin("foo", sdk.NewInt(tokenIn)),
+			tokenOutMinAmount:     sdk.NewInt(tokenInMinAmount),
+			expectedSwapEvents:    1,
+			expectedMessageEvents: 3, // 1 gamm + 2 tendermint.
+		},
+		"two hops": {
+			routes: []types.SwapAmountInRoute{
+				{
+					PoolId:        1,
+					TokenOutDenom: "bar",
+				},
+				{
+					PoolId:        2,
+					TokenOutDenom: "baz",
+				},
+			},
+			tokenIn:               sdk.NewCoin("foo", sdk.NewInt(tokenIn)),
+			tokenOutMinAmount:     sdk.NewInt(tokenInMinAmount),
+			expectedSwapEvents:    2,
+			expectedMessageEvents: 5, // 1 gamm + 4 tendermint.
+		},
+		"invalid - two hops, denom does not exist": {
+			routes: []types.SwapAmountInRoute{
+				{
+					PoolId:        1,
+					TokenOutDenom: "bar",
+				},
+				{
+					PoolId:        2,
+					TokenOutDenom: "baz",
+				},
+			},
+			tokenIn:           sdk.NewCoin(doesNotExistDenom, sdk.NewInt(tokenIn)),
+			tokenOutMinAmount: sdk.NewInt(tokenInMinAmount),
+			expectError:       true,
+		},
+	}
+
+	for name, tc := range testcases {
+		suite.Run(name, func() {
+			suite.Setup()
+			ctx := suite.Ctx
+
+			suite.PrepareBalancerPool()
+			suite.PrepareBalancerPool()
+
+			msgServer := keeper.NewMsgServerImpl(suite.App.GAMMKeeper)
+
+			// Reset event counts to 0 by creating a new manager.
+			ctx = ctx.WithEventManager(sdk.NewEventManager())
+			suite.Equal(0, len(ctx.EventManager().Events()))
+
+			response, err := msgServer.SwapExactAmountIn(sdk.WrapSDKContext(ctx), &types.MsgSwapExactAmountIn{
+				Sender:            suite.TestAccs[0].String(),
+				Routes:            tc.routes,
+				TokenIn:           tc.tokenIn,
+				TokenOutMinAmount: tc.tokenOutMinAmount,
+			})
+
+			if !tc.expectError {
+				suite.NoError(err)
+				suite.NotNil(response)
+			}
+
+			assertEventEmitted(suite, ctx, types.TypeEvtTokenSwapped, tc.expectedSwapEvents)
+			assertEventEmitted(suite, ctx, sdk.EventTypeMessage, tc.expectedMessageEvents)
+		})
+	}
+}
+
+// TestSwapExactAmountOut_Events tests that events are correctly emitted
+// when calling SwapExactAmountOut.
+func (suite *KeeperTestSuite) TestSwapExactAmountOut_Events() {
+	const (
+		// Max positive int64.
+		tokenInMaxAmount = int64(^uint64(0) >> 1)
+		tokenOut         = 5
+	)
+
+	testcases := map[string]struct {
+		routes                []types.SwapAmountOutRoute
+		tokenOut              sdk.Coin
+		tokenInMaxAmount      sdk.Int
+		expectError           bool
+		expectedSwapEvents    int
+		expectedMessageEvents int
+	}{
+		"zero hops": {
+			routes:                []types.SwapAmountOutRoute{},
+			tokenOut:              sdk.NewCoin("foo", sdk.NewInt(tokenOut)),
+			tokenInMaxAmount:      sdk.NewInt(tokenInMaxAmount),
+			expectedMessageEvents: 1, // 1 gamm.
+		},
+		"one hop": {
+			routes: []types.SwapAmountOutRoute{
+				{
+					PoolId:       1,
+					TokenInDenom: "bar",
+				},
+			},
+			tokenOut:              sdk.NewCoin("foo", sdk.NewInt(tokenOut)),
+			tokenInMaxAmount:      sdk.NewInt(tokenInMaxAmount),
+			expectedSwapEvents:    1,
+			expectedMessageEvents: 3, // 1 gamm + 2 tendermint.
+		},
+		"two hops": {
+			routes: []types.SwapAmountOutRoute{
+				{
+					PoolId:       1,
+					TokenInDenom: "bar",
+				},
+				{
+					PoolId:       2,
+					TokenInDenom: "baz",
+				},
+			},
+			tokenOut:              sdk.NewCoin("foo", sdk.NewInt(tokenOut)),
+			tokenInMaxAmount:      sdk.NewInt(tokenInMaxAmount),
+			expectedSwapEvents:    2,
+			expectedMessageEvents: 5, // 1 gamm + 4 tendermint.
+		},
+		"invalid - two hops, denom does not exist": {
+			routes: []types.SwapAmountOutRoute{
+				{
+					PoolId:       1,
+					TokenInDenom: "bar",
+				},
+				{
+					PoolId:       2,
+					TokenInDenom: "baz",
+				},
+			},
+			tokenOut:         sdk.NewCoin(doesNotExistDenom, sdk.NewInt(tokenOut)),
+			tokenInMaxAmount: sdk.NewInt(tokenInMaxAmount),
+			expectError:      true,
+		},
+	}
+
+	for name, tc := range testcases {
+		suite.Run(name, func() {
+			suite.Setup()
+			ctx := suite.Ctx
+
+			suite.PrepareBalancerPool()
+			suite.PrepareBalancerPool()
+
+			msgServer := keeper.NewMsgServerImpl(suite.App.GAMMKeeper)
+
+			// Reset event counts to 0 by creating a new manager.
+			ctx = ctx.WithEventManager(sdk.NewEventManager())
+			suite.Equal(0, len(ctx.EventManager().Events()))
+
+			response, err := msgServer.SwapExactAmountOut(sdk.WrapSDKContext(ctx), &types.MsgSwapExactAmountOut{
+				Sender:           suite.TestAccs[0].String(),
+				Routes:           tc.routes,
+				TokenOut:         tc.tokenOut,
+				TokenInMaxAmount: tc.tokenInMaxAmount,
+			})
+
+			if !tc.expectError {
+				suite.NoError(err)
+				suite.NotNil(response)
+			}
+
+			assertEventEmitted(suite, ctx, types.TypeEvtTokenSwapped, tc.expectedSwapEvents)
+			assertEventEmitted(suite, ctx, sdk.EventTypeMessage, tc.expectedMessageEvents)
+		})
+	}
+}
+
+func assertEventEmitted(suite *KeeperTestSuite, ctx sdk.Context, eventTypeExpected string, numEventsExpected int) {
+	allEvents := ctx.EventManager().Events()
+	// filter out other events
+	actualEvents := make([]sdk.Event, 0, 1)
+	for _, event := range allEvents {
+		if event.Type == eventTypeExpected {
+			actualEvents = append(actualEvents, event)
+		}
+	}
+	suite.Equal(numEventsExpected, len(actualEvents))
+}
diff --git a/x/gamm/keeper/pool_service_test.go b/x/gamm/keeper/pool_service_test.go
index 343aded9f..2e8492ee9 100644
--- a/x/gamm/keeper/pool_service_test.go
+++ b/x/gamm/keeper/pool_service_test.go
@@ -579,10 +579,10 @@ func (suite *KeeperTestSuite) TestJoinPoolExitPool_InverseRelationship() {
 			poolId, err := suite.App.GAMMKeeper.CreatePool(suite.Ctx, tc.pool)
 			suite.Require().NoError(err)
 
-		  balanceBeforeJoin := suite.App.BankKeeper.GetAllBalances(suite.Ctx, joinPoolAcc)
+			balanceBeforeJoin := suite.App.BankKeeper.GetAllBalances(suite.Ctx, joinPoolAcc)
 
-		  _, _, err = suite.App.GAMMKeeper.JoinPoolNoSwap(suite.Ctx, joinPoolAcc, poolId, tc.joinPoolShareAmt, sdk.Coins{})
-		  suite.Require().NoError(err)
+			_, _, err = suite.App.GAMMKeeper.JoinPoolNoSwap(suite.Ctx, joinPoolAcc, poolId, tc.joinPoolShareAmt, sdk.Coins{})
+			suite.Require().NoError(err)
 
 			_, err = suite.App.GAMMKeeper.ExitPool(suite.Ctx, joinPoolAcc, poolId, tc.joinPoolShareAmt, sdk.Coins{})
 
diff --git a/x/gamm/keeper/swap_test.go b/x/gamm/keeper/swap_test.go
index 4b4f9beb1..9b647d093 100644
--- a/x/gamm/keeper/swap_test.go
+++ b/x/gamm/keeper/swap_test.go
@@ -4,10 +4,14 @@ import (
 	"time"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/stretchr/testify/suite"
 
 	"github.com/osmosis-labs/osmosis/v7/x/gamm/pool-models/balancer"
+	"github.com/osmosis-labs/osmosis/v7/x/gamm/types"
 )
 
+var _ = suite.TestingSuite(nil)
+
 func (suite *KeeperTestSuite) TestBalancerPoolSimpleSwapExactAmountIn() {
 	type param struct {
 		tokenIn           sdk.Coin
@@ -80,29 +84,34 @@ func (suite *KeeperTestSuite) TestBalancerPoolSimpleSwapExactAmountIn() {
 	}
 
 	for _, test := range tests {
-		// Init suite for each test.
-		suite.SetupTest()
-		poolId := suite.PrepareBalancerPool()
-		keeper := suite.App.GAMMKeeper
-
-		if test.expectPass {
-			spotPriceBefore, err := keeper.CalculateSpotPrice(suite.Ctx, poolId, test.param.tokenIn.Denom, test.param.tokenOutDenom)
-			suite.NoError(err, "test: %v", test.name)
-
-			tokenOutAmount, err := keeper.SwapExactAmountIn(suite.Ctx, suite.TestAccs[0], poolId, test.param.tokenIn, test.param.tokenOutDenom, test.param.tokenOutMinAmount)
-			suite.NoError(err, "test: %v", test.name)
-			suite.True(tokenOutAmount.Equal(test.param.expectedTokenOut), "test: %v", test.name)
-
-			spotPriceAfter, err := keeper.CalculateSpotPrice(suite.Ctx, poolId, test.param.tokenIn.Denom, test.param.tokenOutDenom)
-			suite.NoError(err, "test: %v", test.name)
-
-			// Ratio of the token out should be between the before spot price and after spot price.
-			tradeAvgPrice := test.param.tokenIn.Amount.ToDec().Quo(tokenOutAmount.ToDec())
-			suite.True(tradeAvgPrice.GT(spotPriceBefore) && tradeAvgPrice.LT(spotPriceAfter), "test: %v", test.name)
-		} else {
-			_, err := keeper.SwapExactAmountIn(suite.Ctx, suite.TestAccs[0], poolId, test.param.tokenIn, test.param.tokenOutDenom, test.param.tokenOutMinAmount)
-			suite.Error(err, "test: %v", test.name)
-		}
+		suite.Run(test.name, func() {
+			// Init suite for each test.
+			suite.SetupTest()
+			poolId := suite.PrepareBalancerPool()
+			keeper := suite.App.GAMMKeeper
+			ctx := suite.Ctx
+
+			if test.expectPass {
+				spotPriceBefore, err := keeper.CalculateSpotPrice(ctx, poolId, test.param.tokenIn.Denom, test.param.tokenOutDenom)
+				suite.NoError(err, "test: %v", test.name)
+
+				tokenOutAmount, err := keeper.SwapExactAmountIn(ctx, suite.TestAccs[0], poolId, test.param.tokenIn, test.param.tokenOutDenom, test.param.tokenOutMinAmount)
+				suite.NoError(err, "test: %v", test.name)
+				suite.True(tokenOutAmount.Equal(test.param.expectedTokenOut), "test: %v", test.name)
+
+				assertEventEmitted(suite, ctx, types.TypeEvtTokenSwapped, 1)
+
+				spotPriceAfter, err := keeper.CalculateSpotPrice(ctx, poolId, test.param.tokenIn.Denom, test.param.tokenOutDenom)
+				suite.NoError(err, "test: %v", test.name)
+
+				// Ratio of the token out should be between the before spot price and after spot price.
+				tradeAvgPrice := test.param.tokenIn.Amount.ToDec().Quo(tokenOutAmount.ToDec())
+				suite.True(tradeAvgPrice.GT(spotPriceBefore) && tradeAvgPrice.LT(spotPriceAfter), "test: %v", test.name)
+			} else {
+				_, err := keeper.SwapExactAmountIn(ctx, suite.TestAccs[0], poolId, test.param.tokenIn, test.param.tokenOutDenom, test.param.tokenOutMinAmount)
+				suite.Error(err, "test: %v", test.name)
+			}
+		})
 	}
 }
 
@@ -178,32 +187,37 @@ func (suite *KeeperTestSuite) TestBalancerPoolSimpleSwapExactAmountOut() {
 	}
 
 	for _, test := range tests {
-		// Init suite for each test.
-		suite.SetupTest()
-		poolId := suite.PrepareBalancerPool()
+		suite.Run(test.name, func() {
+			// Init suite for each test.
+			suite.SetupTest()
+			poolId := suite.PrepareBalancerPool()
 
-		keeper := suite.App.GAMMKeeper
+			keeper := suite.App.GAMMKeeper
+			ctx := suite.Ctx
 
-		if test.expectPass {
-			spotPriceBefore, err := keeper.CalculateSpotPrice(suite.Ctx, poolId, test.param.tokenInDenom, test.param.tokenOut.Denom)
-			suite.NoError(err, "test: %v", test.name)
+			if test.expectPass {
+				spotPriceBefore, err := keeper.CalculateSpotPrice(ctx, poolId, test.param.tokenInDenom, test.param.tokenOut.Denom)
+				suite.NoError(err, "test: %v", test.name)
 
-			tokenInAmount, err := keeper.SwapExactAmountOut(suite.Ctx, suite.TestAccs[0], poolId, test.param.tokenInDenom, test.param.tokenInMaxAmount, test.param.tokenOut)
-			suite.NoError(err, "test: %v", test.name)
-			suite.True(tokenInAmount.Equal(test.param.expectedTokenInAmount),
-				"test: %v\n expect_eq actual: %s, expected: %s",
-				test.name, tokenInAmount, test.param.expectedTokenInAmount)
+				tokenInAmount, err := keeper.SwapExactAmountOut(ctx, suite.TestAccs[0], poolId, test.param.tokenInDenom, test.param.tokenInMaxAmount, test.param.tokenOut)
+				suite.NoError(err, "test: %v", test.name)
+				suite.True(tokenInAmount.Equal(test.param.expectedTokenInAmount),
+					"test: %v\n expect_eq actual: %s, expected: %s",
+					test.name, tokenInAmount, test.param.expectedTokenInAmount)
 
-			spotPriceAfter, err := keeper.CalculateSpotPrice(suite.Ctx, poolId, test.param.tokenInDenom, test.param.tokenOut.Denom)
-			suite.NoError(err, "test: %v", test.name)
+				assertEventEmitted(suite, ctx, types.TypeEvtTokenSwapped, 1)
 
-			// Ratio of the token out should be between the before spot price and after spot price.
-			tradeAvgPrice := tokenInAmount.ToDec().Quo(test.param.tokenOut.Amount.ToDec())
-			suite.True(tradeAvgPrice.GT(spotPriceBefore) && tradeAvgPrice.LT(spotPriceAfter), "test: %v", test.name)
-		} else {
-			_, err := keeper.SwapExactAmountOut(suite.Ctx, suite.TestAccs[0], poolId, test.param.tokenInDenom, test.param.tokenInMaxAmount, test.param.tokenOut)
-			suite.Error(err, "test: %v", test.name)
-		}
+				spotPriceAfter, err := keeper.CalculateSpotPrice(ctx, poolId, test.param.tokenInDenom, test.param.tokenOut.Denom)
+				suite.NoError(err, "test: %v", test.name)
+
+				// Ratio of the token out should be between the before spot price and after spot price.
+				tradeAvgPrice := tokenInAmount.ToDec().Quo(test.param.tokenOut.Amount.ToDec())
+				suite.True(tradeAvgPrice.GT(spotPriceBefore) && tradeAvgPrice.LT(spotPriceAfter), "test: %v", test.name)
+			} else {
+				_, err := keeper.SwapExactAmountOut(suite.Ctx, suite.TestAccs[0], poolId, test.param.tokenInDenom, test.param.tokenInMaxAmount, test.param.tokenOut)
+				suite.Error(err, "test: %v", test.name)
+			}
+		})
 	}
 }
 
diff --git a/x/gamm/pool-models/balancer/pool_test.go b/x/gamm/pool-models/balancer/pool_test.go
index c7d6ad8ae..fd13bd776 100644
--- a/x/gamm/pool-models/balancer/pool_test.go
+++ b/x/gamm/pool-models/balancer/pool_test.go
@@ -33,131 +33,98 @@ var (
 // TestUpdateIntermediaryPoolAssetsLiquidity tests if `updateIntermediaryPoolAssetsLiquidity` returns poolAssetsByDenom map
 // with the updated liquidity given by the parameter
 func TestUpdateIntermediaryPoolAssetsLiquidity(t *testing.T) {
-	testCases := []struct {
-		name string
-
-		// returns newLiquidity, originalPoolAssetsByDenom, expectedPoolAssetsByDenom
-		setup func() (sdk.Coins, map[string]balancer.PoolAsset, map[string]balancer.PoolAsset)
+	const (
+		uosmoValueOriginal = 1_000_000_000_000
+		atomValueOriginal  = 123
+		ionValueOriginal   = 657
 
-		err error
+		// Weight does not affect calculations so it is shared
+		weight = 100
+	)
+	testCases := []struct {
+		name         string
+		newLiquidity sdk.Coins
+		poolAssets   map[string]balancer.PoolAsset
+		expectPass   bool
+		err          error
 	}{
 		{
 			name: "regular case with multiple pool assets and a subset of newLiquidity to update",
-
-			setup: func() (sdk.Coins, map[string]balancer.PoolAsset, map[string]balancer.PoolAsset) {
-				const (
-					uosmoValueOriginal = 1_000_000_000_000
-					atomValueOriginal  = 123
-					ionValueOriginal   = 657
-
-					// Weight does not affect calculations so it is shared
-					weight = 100
-				)
-
-				newLiquidity := sdk.NewCoins(
-					sdk.NewInt64Coin("uosmo", 1_000),
-					sdk.NewInt64Coin("atom", 2_000),
-					sdk.NewInt64Coin("ion", 3_000))
-
-				originalPoolAssetsByDenom := map[string]balancer.PoolAsset{
-					"uosmo": {
-						Token:  sdk.NewInt64Coin("uosmo", uosmoValueOriginal),
-						Weight: sdk.NewInt(weight),
-					},
-					"atom": {
-						Token:  sdk.NewInt64Coin("atom", atomValueOriginal),
-						Weight: sdk.NewInt(weight),
-					},
-					"ion": {
-						Token:  sdk.NewInt64Coin("ion", ionValueOriginal),
-						Weight: sdk.NewInt(weight),
-					},
-				}
-
-				expectedPoolAssetsByDenom := map[string]balancer.PoolAsset{}
-				for k, v := range originalPoolAssetsByDenom {
-					expectedValue := balancer.PoolAsset{Token: v.Token, Weight: v.Weight}
-					expectedValue.Token.Amount = expectedValue.Token.Amount.Add(newLiquidity.AmountOf(k))
-					expectedPoolAssetsByDenom[k] = expectedValue
-				}
-
-				return newLiquidity, originalPoolAssetsByDenom, expectedPoolAssetsByDenom
+			newLiquidity: sdk.NewCoins(
+				sdk.NewInt64Coin("uosmo", 1_000),
+				sdk.NewInt64Coin("atom", 2_000),
+				sdk.NewInt64Coin("ion", 3_000)),
+			poolAssets: map[string]balancer.PoolAsset{
+				"uosmo": {
+					Token:  sdk.NewInt64Coin("uosmo", uosmoValueOriginal),
+					Weight: sdk.NewInt(weight),
+				},
+				"atom": {
+					Token:  sdk.NewInt64Coin("atom", atomValueOriginal),
+					Weight: sdk.NewInt(weight),
+				},
+				"ion": {
+					Token:  sdk.NewInt64Coin("ion", ionValueOriginal),
+					Weight: sdk.NewInt(weight),
+				},
 			},
+			expectPass: true,
 		},
 		{
-			name: "new liquidity has no coins",
-
-			setup: func() (sdk.Coins, map[string]balancer.PoolAsset, map[string]balancer.PoolAsset) {
-				const (
-					uosmoValueOriginal = 1_000_000_000_000
-					atomValueOriginal  = 123
-					ionValueOriginal   = 657
-
-					// Weight does not affect calculations so it is shared
-					weight = 100
-				)
-
-				newLiquidity := sdk.NewCoins()
-
-				originalPoolAssetsByDenom := map[string]balancer.PoolAsset{
-					"uosmo": {
-						Token:  sdk.NewInt64Coin("uosmo", uosmoValueOriginal),
-						Weight: sdk.NewInt(weight),
-					},
-					"atom": {
-						Token:  sdk.NewInt64Coin("atom", atomValueOriginal),
-						Weight: sdk.NewInt(weight),
-					},
-					"ion": {
-						Token:  sdk.NewInt64Coin("ion", ionValueOriginal),
-						Weight: sdk.NewInt(weight),
-					},
-				}
-
-				return newLiquidity, originalPoolAssetsByDenom, originalPoolAssetsByDenom
+			name:         "new liquidity has no coins",
+			newLiquidity: sdk.NewCoins(),
+			poolAssets: map[string]balancer.PoolAsset{
+				"uosmo": {
+					Token:  sdk.NewInt64Coin("uosmo", uosmoValueOriginal),
+					Weight: sdk.NewInt(weight),
+				},
+				"atom": {
+					Token:  sdk.NewInt64Coin("atom", atomValueOriginal),
+					Weight: sdk.NewInt(weight),
+				},
+				"ion": {
+					Token:  sdk.NewInt64Coin("ion", ionValueOriginal),
+					Weight: sdk.NewInt(weight),
+				},
 			},
+			expectPass: true,
 		},
 		{
 			name: "newLiquidity has a coin that poolAssets don't",
-
-			setup: func() (sdk.Coins, map[string]balancer.PoolAsset, map[string]balancer.PoolAsset) {
-				const (
-					uosmoValueOriginal = 1_000_000_000_000
-
-					// Weight does not affect calculations so it is shared
-					weight = 100
-				)
-
-				newLiquidity := sdk.NewCoins(
-					sdk.NewInt64Coin("juno", 1_000))
-
-				originalPoolAssetsByDenom := map[string]balancer.PoolAsset{
-					"uosmo": {
-						Token:  sdk.NewInt64Coin("uosmo", uosmoValueOriginal),
-						Weight: sdk.NewInt(weight),
-					},
-				}
-
-				return newLiquidity, originalPoolAssetsByDenom, originalPoolAssetsByDenom
+			newLiquidity: sdk.NewCoins(
+				sdk.NewInt64Coin("juno", 1_000)),
+			poolAssets: map[string]balancer.PoolAsset{
+				"uosmo": {
+					Token:  sdk.NewInt64Coin("uosmo", uosmoValueOriginal),
+					Weight: sdk.NewInt(weight),
+				},
 			},
-
-			err: fmt.Errorf(balancer.ErrMsgFormatFailedInterimLiquidityUpdate, "juno"),
+			expectPass: false,
+			err:        fmt.Errorf(balancer.ErrMsgFormatFailedInterimLiquidityUpdate, "juno"),
 		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			newLiquidity, originalPoolAssetsByDenom, expectedPoolAssetsByDenom := tc.setup()
-
-			err := balancer.UpdateIntermediaryPoolAssetsLiquidity(newLiquidity, originalPoolAssetsByDenom)
+			expectedPoolAssetsByDenom := map[string]balancer.PoolAsset{}
+			for denom, asset := range tc.poolAssets {
+				expectedValue := asset
+				expectedValue.Token.Amount = expectedValue.Token.Amount.Add(tc.newLiquidity.AmountOf(denom))
+				expectedPoolAssetsByDenom[denom] = expectedValue
+			}
 
-			require.Equal(t, tc.err, err)
+			err := balancer.UpdateIntermediaryPoolAssetsLiquidity(tc.newLiquidity, tc.poolAssets)
 
-			if tc.err != nil {
-				return
+			if tc.expectPass {
+				require.NoError(t, tc.err, "test: %v", tc.name)
+				// make sure actual pool assets are properly updated
+				require.Equal(t, expectedPoolAssetsByDenom, tc.poolAssets)
+			} else {
+				require.Error(t, tc.err, "test: %v", tc.name)
+				require.Equal(t, tc.err, err)
+				require.Equal(t, expectedPoolAssetsByDenom, tc.poolAssets)
 			}
-
-			require.Equal(t, expectedPoolAssetsByDenom, originalPoolAssetsByDenom)
+			return
 		})
 	}
 }
diff --git a/x/incentives/client/cli/cli_test.go b/x/incentives/client/cli/cli_test.go
index fce100e12..719307651 100644
--- a/x/incentives/client/cli/cli_test.go
+++ b/x/incentives/client/cli/cli_test.go
@@ -78,7 +78,6 @@ func (s *IntegrationTestSuite) SetupSuite() {
 
 	_, err = s.network.WaitForHeight(1)
 	s.Require().NoError(err)
-
 }
 
 func (s *IntegrationTestSuite) TearDownSuite() {
diff --git a/x/incentives/keeper/distribute_test.go b/x/incentives/keeper/distribute_test.go
index 9699cf05a..59077e2fb 100644
--- a/x/incentives/keeper/distribute_test.go
+++ b/x/incentives/keeper/distribute_test.go
@@ -174,7 +174,6 @@ func (suite *KeeperTestSuite) TestSyntheticDistribute() {
 			suite.Require().Equal(tc.expectedRewards[i].String(), rewards, "test %v, person %d", tc.name, i)
 		}
 	}
-
 }
 
 // TestGetModuleToDistributeCoins tests the sum of coins yet to be distributed for all of the module is correct.
diff --git a/x/incentives/keeper/gauge_test.go b/x/incentives/keeper/gauge_test.go
index 653202ad1..e46fb22cb 100644
--- a/x/incentives/keeper/gauge_test.go
+++ b/x/incentives/keeper/gauge_test.go
@@ -236,5 +236,4 @@ func (suite *KeeperTestSuite) TestGaugeOperations() {
 			suite.Require().Len(gaugeIdsByDenom, 1)
 		}
 	}
-
 }
diff --git a/x/mint/keeper/export_test.go b/x/mint/keeper/export_test.go
index 2dfe83516..67dd378ed 100644
--- a/x/mint/keeper/export_test.go
+++ b/x/mint/keeper/export_test.go
@@ -1,8 +1,14 @@
 package keeper
 
-import sdk "github.com/cosmos/cosmos-sdk/types"
+import (
+	sdk "github.com/cosmos/cosmos-sdk/types"
 
-type ErrInvalidRatio = invalidRatioError
+	"github.com/osmosis-labs/osmosis/v7/x/mint/types"
+)
+
+type (
+	ErrInvalidRatio = invalidRatioError
+)
 
 const (
 	DeveloperVestingAmount = developerVestingAmount
@@ -16,6 +22,10 @@ var (
 	GetProportions = getProportions
 )
 
-func (k Keeper) DistributeToModule(ctx sdk.Context, recipientModule string, mintedCoin sdk.Coin, proportion sdk.Dec) (sdk.Coin, error) {
+func (k Keeper) DistributeToModule(ctx sdk.Context, recipientModule string, mintedCoin sdk.Coin, proportion sdk.Dec) (sdk.Int, error) {
 	return k.distributeToModule(ctx, recipientModule, mintedCoin, proportion)
 }
+
+func (k Keeper) DistributeDeveloperRewards(ctx sdk.Context, totalMintedCoin sdk.Coin, developerRewardsProportion sdk.Dec, developerRewardsReceivers []types.WeightedAddress) (sdk.Int, error) {
+	return k.distributeDeveloperRewards(ctx, totalMintedCoin, developerRewardsProportion, developerRewardsReceivers)
+}
diff --git a/x/mint/keeper/keeper_test.go b/x/mint/keeper/keeper_test.go
index c619af246..73baaa8c3 100644
--- a/x/mint/keeper/keeper_test.go
+++ b/x/mint/keeper/keeper_test.go
@@ -1,12 +1,16 @@
 package keeper_test
 
 import (
+	"fmt"
 	"testing"
 	"time"
 
+	"github.com/cosmos/btcutil/bech32"
 	sdk "github.com/cosmos/cosmos-sdk/types"
+	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
 	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
 	"github.com/cosmos/cosmos-sdk/x/distribution"
+	distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
 
 	"github.com/stretchr/testify/suite"
 	abci "github.com/tendermint/tendermint/abci/types"
@@ -25,6 +29,13 @@ type KeeperTestSuite struct {
 	queryClient types.QueryClient
 }
 
+var (
+	testAddressOne   = sdk.AccAddress([]byte("addr1---------------"))
+	testAddressTwo   = sdk.AccAddress([]byte("addr2---------------"))
+	testAddressThree = sdk.AccAddress([]byte("addr3---------------"))
+	testAddressFour  = sdk.AccAddress([]byte("addr4---------------"))
+)
+
 func TestKeeperTestSuite(t *testing.T) {
 	suite.Run(t, new(KeeperTestSuite))
 }
@@ -133,7 +144,7 @@ func (suite *KeeperTestSuite) TestGetProportions() {
 	}
 }
 
-func (suite *KeeperTestSuite) TestDistrAssetToDeveloperRewardsAddr() {
+func (suite *KeeperTestSuite) TestDistributeMintedCoin_ToDeveloperRewardsAddr() {
 	var (
 		distrTo = lockuptypes.QueryCondition{
 			LockQueryType: lockuptypes.ByDuration,
@@ -142,7 +153,7 @@ func (suite *KeeperTestSuite) TestDistrAssetToDeveloperRewardsAddr() {
 		}
 		params       = suite.App.MintKeeper.GetParams(suite.Ctx)
 		gaugeCoins   = sdk.Coins{sdk.NewInt64Coin("stake", 10000)}
-		gaugeCreator = sdk.AccAddress([]byte("addr2---------------"))
+		gaugeCreator = testAddressTwo
 		mintLPtokens = sdk.Coins{sdk.NewInt64Coin(distrTo.Denom, 200)}
 	)
 
@@ -155,22 +166,24 @@ func (suite *KeeperTestSuite) TestDistrAssetToDeveloperRewardsAddr() {
 			name: "one dev reward address",
 			weightedAddresses: []types.WeightedAddress{
 				{
-					Address: sdk.AccAddress([]byte("addr1---------------")).String(),
+					Address: testAddressOne.String(),
 					Weight:  sdk.NewDec(1),
-				}},
+				},
+			},
 			mintCoin: sdk.NewCoin("stake", sdk.NewInt(10000)),
 		},
 		{
 			name: "multiple dev reward addresses",
 			weightedAddresses: []types.WeightedAddress{
 				{
-					Address: sdk.AccAddress([]byte("addr3---------------")).String(),
+					Address: testAddressThree.String(),
 					Weight:  sdk.NewDecWithPrec(6, 1),
 				},
 				{
-					Address: sdk.AccAddress([]byte("addr4---------------")).String(),
+					Address: testAddressFour.String(),
 					Weight:  sdk.NewDecWithPrec(4, 1),
-				}},
+				},
+			},
 			mintCoin: sdk.NewCoin("stake", sdk.NewInt(100000)),
 		},
 		{
@@ -225,7 +238,7 @@ func (suite *KeeperTestSuite) TestDistrAssetToDeveloperRewardsAddr() {
 					feePool.CommunityPool.AmountOf("stake"))
 			} else {
 				suite.Equal(
-					//distribution go to community pool because nil dev reward addresses.
+					// distribution go to community pool because nil dev reward addresses.
 					tc.mintCoin.Amount.ToDec().Mul((params.DistributionProportions.DeveloperRewards).Add(params.DistributionProportions.CommunityPool)),
 					feePool.CommunityPool.AmountOf("stake"))
 			}
@@ -389,7 +402,7 @@ func (suite *KeeperTestSuite) TestDistributeToModule() {
 	)
 
 	tests := map[string]struct {
-		preMintAmount sdk.Coin
+		preMintCoin sdk.Coin
 
 		recepientModule string
 		mintedCoin      sdk.Coin
@@ -399,21 +412,21 @@ func (suite *KeeperTestSuite) TestDistributeToModule() {
 		expectPanic   bool
 	}{
 		"pre-mint == distribute - poolincentives module - full amount - success": {
-			preMintAmount: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
+			preMintCoin: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
 
 			recepientModule: poolincentivestypes.ModuleName,
 			mintedCoin:      sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
 			proportion:      sdk.NewDec(1),
 		},
 		"pre-mint > distribute - developer vesting module - two thirds - success": {
-			preMintAmount: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(101)),
+			preMintCoin: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(101)),
 
 			recepientModule: poolincentivestypes.ModuleName,
 			mintedCoin:      sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
 			proportion:      sdk.NewDecWithPrec(2, 1).Quo(sdk.NewDecWithPrec(3, 1)),
 		},
 		"pre-mint < distribute (0) - error": {
-			preMintAmount: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(0)),
+			preMintCoin: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(0)),
 
 			recepientModule: poolincentivestypes.ModuleName,
 			mintedCoin:      sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
@@ -422,7 +435,7 @@ func (suite *KeeperTestSuite) TestDistributeToModule() {
 			expectedError: true,
 		},
 		"denom does not exist - error": {
-			preMintAmount: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
+			preMintCoin: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
 
 			recepientModule: poolincentivestypes.ModuleName,
 			mintedCoin:      sdk.NewCoin(denomDoesNotExist, sdk.NewInt(100)),
@@ -431,7 +444,7 @@ func (suite *KeeperTestSuite) TestDistributeToModule() {
 			expectedError: true,
 		},
 		"invalid module account -panic": {
-			preMintAmount: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
+			preMintCoin: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
 
 			recepientModule: moduleAccountDoesNotExist,
 			mintedCoin:      sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
@@ -440,7 +453,7 @@ func (suite *KeeperTestSuite) TestDistributeToModule() {
 			expectPanic: true,
 		},
 		"proportion greater than 1 - error": {
-			preMintAmount: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(300)),
+			preMintCoin: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(300)),
 
 			recepientModule: poolincentivestypes.ModuleName,
 			mintedCoin:      sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
@@ -459,37 +472,289 @@ func (suite *KeeperTestSuite) TestDistributeToModule() {
 				ctx := suite.Ctx
 
 				// Setup.
-				suite.NoError(mintKeeper.MintCoins(ctx, sdk.NewCoins(tc.preMintAmount)))
+				suite.NoError(mintKeeper.MintCoins(ctx, sdk.NewCoins(tc.preMintCoin)))
 
 				// TODO: Should not be truncated. Remove truncation after rounding errors are addressed and resolved.
 				// Ref: https://github.com/osmosis-labs/osmosis/issues/1917
 				expectedDistributed := tc.mintedCoin.Amount.ToDec().Mul(tc.proportion).TruncateInt()
-				oldMintModuleBalance := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(types.ModuleName), tc.mintedCoin.Denom)
-				oldRecepientModuleBalance := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(tc.recepientModule), tc.mintedCoin.Denom)
+				oldMintModuleBalanceAmount := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(types.ModuleName), tc.mintedCoin.Denom).Amount
+				oldRecepientModuleBalanceAmount := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(tc.recepientModule), tc.mintedCoin.Denom).Amount
 
 				// Test.
 				actualDistributed, err := mintKeeper.DistributeToModule(ctx, tc.recepientModule, tc.mintedCoin, tc.proportion)
 
 				// Assertions.
-				actualMintModuleBalance := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(types.ModuleName), tc.mintedCoin.Denom)
-				actualRecepientModuleBalance := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(tc.recepientModule), tc.mintedCoin.Denom)
+				actualMintModuleBalanceAmount := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(types.ModuleName), tc.mintedCoin.Denom).Amount
+				actualRecepientModuleBalanceAmount := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(tc.recepientModule), tc.mintedCoin.Denom).Amount
 
 				if tc.expectedError {
 					suite.Error(err)
-					suite.Equal(actualDistributed, sdk.Coin{})
+					suite.Equal(actualDistributed, sdk.Int{})
 					// Old balances should not change.
-					suite.Equal(oldMintModuleBalance.Amount.Int64(), actualMintModuleBalance.Amount.Int64())
-					suite.Equal(oldRecepientModuleBalance.Amount.Int64(), actualRecepientModuleBalance.Amount.Int64())
+					suite.Equal(oldMintModuleBalanceAmount.Int64(), actualMintModuleBalanceAmount.Int64())
+					suite.Equal(oldRecepientModuleBalanceAmount.Int64(), actualRecepientModuleBalanceAmount.Int64())
 					return
 				}
 
 				suite.NoError(err)
-				suite.Equal(tc.mintedCoin.Denom, actualDistributed.Denom)
-				suite.Equal(expectedDistributed, actualDistributed.Amount)
+				suite.Equal(expectedDistributed, actualDistributed)
 
 				// Updated balances.
-				suite.Equal(oldMintModuleBalance.Sub(actualDistributed).Amount.Int64(), actualMintModuleBalance.Amount.Int64())
-				suite.Equal(oldRecepientModuleBalance.Add(actualDistributed).Amount.Int64(), actualRecepientModuleBalance.Amount.Int64())
+				suite.Equal(oldMintModuleBalanceAmount.Sub(actualDistributed).Int64(), actualMintModuleBalanceAmount.Int64())
+				suite.Equal(oldRecepientModuleBalanceAmount.Add(actualDistributed).Int64(), actualRecepientModuleBalanceAmount.Int64())
+			})
+		})
+	}
+}
+
+// TestDistributeDeveloperRewards tests the following:
+// - distribution from developer module account to the given weighted addressed occurs.
+// - developer vesting module account balance is correctly updated.
+// - all developer addressed are updated with correct proportions.
+// - mint module account balance is updated - burn over allocations.
+// - if recepients are empty - community pool us updated.
+func (suite *KeeperTestSuite) TestDistributeDeveloperRewards() {
+	const (
+		invalidAddress = "invalid"
+	)
+
+	var (
+		validLargePreMintAmount  = sdk.NewInt(keeper.DeveloperVestingAmount)
+		validPreMintAmountAddOne = sdk.NewInt(keeper.DeveloperVestingAmount).Add(sdk.OneInt())
+		validPreMintCoin         = sdk.NewCoin(sdk.DefaultBondDenom, validLargePreMintAmount)
+		validPreMintCoinSubOne   = sdk.NewCoin(sdk.DefaultBondDenom, validLargePreMintAmount.Sub(sdk.OneInt()))
+	)
+
+	tests := map[string]struct {
+		preMintCoin sdk.Coin
+
+		mintedCoin         sdk.Coin
+		proportion         sdk.Dec
+		recepientAddresses []types.WeightedAddress
+
+		expectedError error
+		expectPanic   bool
+		// See testcases with this flag set to true for details.
+		allowBalanceChange bool
+		// See testcases with this flag set to true for details.
+		expectSameAddresses bool
+	}{
+		"valid case with 1 weighted address": {
+			preMintCoin: validPreMintCoin,
+
+			mintedCoin: validPreMintCoin,
+			proportion: sdk.NewDecWithPrec(153, 3),
+			recepientAddresses: []types.WeightedAddress{
+				{
+					Address: testAddressOne.String(),
+					Weight:  sdk.NewDec(1),
+				},
+			},
+		},
+		"valid case with 3 weighted addresses and custom large mint amount under pre mint": {
+			preMintCoin: validPreMintCoin,
+
+			mintedCoin: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(939_123_546_789)),
+			proportion: sdk.NewDecWithPrec(31347, 5),
+			recepientAddresses: []types.WeightedAddress{ // .231 + .4 + .369
+				{
+					Address: testAddressOne.String(),
+					Weight:  sdk.NewDecWithPrec(231, 3),
+				},
+				{
+					Address: testAddressTwo.String(),
+					Weight:  sdk.NewDecWithPrec(4, 1),
+				},
+				{
+					Address: testAddressThree.String(),
+					Weight:  sdk.NewDecWithPrec(369, 3),
+				},
+			},
+		},
+		"valid case with 2 addresses that are the same": {
+			preMintCoin: validPreMintCoin,
+
+			mintedCoin: validPreMintCoin,
+			proportion: sdk.NewDecWithPrec(123, 3),
+			recepientAddresses: []types.WeightedAddress{
+				{
+					Address: testAddressOne.String(),
+					Weight:  sdk.NewDecWithPrec(5, 1),
+				},
+				{
+					Address: testAddressOne.String(),
+					Weight:  sdk.NewDecWithPrec(5, 1),
+				},
+			},
+			// Since we have double the full amount allocated
+			/// to the same address, the balance assertions will
+			// differ by expecting the full minted amount.
+			expectSameAddresses: true,
+		},
+		"valid case with 0 reward receivers - goes to community pool": {
+			preMintCoin: validPreMintCoin,
+
+			mintedCoin: validPreMintCoin,
+			proportion: sdk.NewDecWithPrec(153, 3),
+		},
+		"valid case with 0 amount of total minted coin": {
+			preMintCoin: validPreMintCoin,
+
+			mintedCoin: sdk.NewCoin(sdk.DefaultBondDenom, sdk.ZeroInt()),
+			proportion: sdk.NewDecWithPrec(153, 3),
+			recepientAddresses: []types.WeightedAddress{
+				{
+					Address: testAddressOne.String(),
+					Weight:  sdk.NewDec(1),
+				},
+			},
+		},
+		"invalid value for developer rewards proportion (> 1) - error": {
+			preMintCoin: validPreMintCoin,
+
+			mintedCoin: validPreMintCoin,
+			proportion: sdk.NewDec(2),
+			recepientAddresses: []types.WeightedAddress{
+				{
+					Address: testAddressOne.String(),
+					Weight:  sdk.NewDec(1),
+				},
+			},
+
+			expectedError: keeper.ErrInvalidRatio{ActualRatio: sdk.NewDec(2)},
+		},
+		"invalid address in developer reward receivers - error": {
+			preMintCoin: validPreMintCoin,
+
+			mintedCoin: validPreMintCoin,
+			proportion: sdk.NewDecWithPrec(153, 3),
+			recepientAddresses: []types.WeightedAddress{
+				{
+					Address: invalidAddress,
+					Weight:  sdk.NewDec(1),
+				},
+			},
+
+			expectedError: sdkerrors.Wrap(bech32.ErrInvalidLength(len(invalidAddress)), "decoding bech32 failed"),
+			// This case should not happen in practice due to parameter validation.
+			// The method spec also requires that all recepient addresses are valid by CONTRACT.
+			// Since we still handle error returned by the converion from string to address,
+			// we try to cover it explicitly. However, it changes balance so we don't test it.
+			allowBalanceChange: true,
+		},
+		"pre-mint < distribute * proportion - error": {
+			preMintCoin: validPreMintCoinSubOne,
+
+			mintedCoin: validPreMintCoin,
+			proportion: sdk.OneDec(),
+			recepientAddresses: []types.WeightedAddress{
+				{
+					Address: testAddressOne.String(),
+					Weight:  sdk.NewDec(1),
+				},
+			},
+			expectedError: sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, fmt.Sprintf("%s is smaller than %s", validPreMintCoinSubOne, validPreMintCoin)),
+		},
+		"distribute * proportion < pre-mint but distribute * proportion > developer vesting amount - error": {
+			preMintCoin: validPreMintCoin,
+
+			mintedCoin: sdk.NewCoin(sdk.DefaultBondDenom, validPreMintAmountAddOne),
+			proportion: sdk.OneDec(),
+			recepientAddresses: []types.WeightedAddress{
+				{
+					Address: testAddressOne.String(),
+					Weight:  sdk.NewDec(1),
+				},
+			},
+			expectedError: sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, fmt.Sprintf("%s is smaller than %s", validPreMintCoin, sdk.NewCoin(sdk.DefaultBondDenom, validPreMintAmountAddOne))),
+		},
+	}
+	for name, tc := range tests {
+		suite.Run(name, func() {
+			suite.Setup()
+
+			osmoutils.ConditionalPanic(suite.T(), tc.expectPanic, func() {
+				mintKeeper := suite.App.MintKeeper
+				bankKeeper := suite.App.BankKeeper
+				accountKeeper := suite.App.AccountKeeper
+				ctx := suite.Ctx
+
+				// Setup.
+				suite.NoError(mintKeeper.MintCoins(ctx, sdk.NewCoins(tc.preMintCoin)))
+
+				// TODO: Should not be truncated. Remove truncation after rounding errors are addressed and resolved.
+				// Ref: https://github.com/osmosis-labs/osmosis/issues/1917
+				expectedDistributed := tc.mintedCoin.Amount.ToDec().Mul(tc.proportion).TruncateInt()
+
+				oldMintModuleBalanceAmount := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(types.ModuleName), tc.mintedCoin.Denom).Amount
+				oldDeveloperVestingModuleBalanceAmount := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(types.DeveloperVestingModuleAcctName), tc.mintedCoin.Denom).Amount
+				oldCommunityPoolBalanceAmount := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(distributiontypes.ModuleName), tc.mintedCoin.Denom).Amount
+				oldDeveloperRewardsBalanceAmounts := make([]sdk.Int, len(tc.recepientAddresses))
+				for i, weightedAddress := range tc.recepientAddresses {
+					// No error check to be able to test invalid addresses.
+					address, _ := sdk.AccAddressFromBech32(weightedAddress.Address)
+					oldDeveloperRewardsBalanceAmounts[i] = bankKeeper.GetBalance(ctx, address, tc.mintedCoin.Denom).Amount
+				}
+
+				// Test.
+				actualDistributed, err := mintKeeper.DistributeDeveloperRewards(ctx, tc.mintedCoin, tc.proportion, tc.recepientAddresses)
+
+				// Assertions.
+				actualMintModuleBalance := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(types.ModuleName), tc.mintedCoin.Denom)
+				actualDeveloperVestingModuleBalanceAmount := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(types.DeveloperVestingModuleAcctName), tc.mintedCoin.Denom).Amount
+				actualCommunityPoolModuleBalanceAmount := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(distributiontypes.ModuleName), tc.mintedCoin.Denom).Amount
+
+				if tc.expectedError != nil {
+					suite.Error(err)
+					suite.Equal(tc.expectedError.Error(), err.Error())
+					suite.Equal(actualDistributed, sdk.Int{})
+
+					// See testcases with this flag set to true for details.
+					if tc.allowBalanceChange {
+						return
+					}
+					// Old balances should not change.
+					suite.Equal(oldMintModuleBalanceAmount.Int64(), actualMintModuleBalance.Amount.Int64())
+					suite.Equal(oldDeveloperVestingModuleBalanceAmount.Int64(), actualDeveloperVestingModuleBalanceAmount.Int64())
+					suite.Equal(oldCommunityPoolBalanceAmount.Int64(), actualCommunityPoolModuleBalanceAmount.Int64())
+					return
+				}
+
+				suite.NoError(err)
+				suite.Equal(expectedDistributed, actualDistributed)
+
+				// Updated balances.
+
+				// Burn from mint module account. We over-allocate.
+				// To be fixed: https://github.com/osmosis-labs/osmosis/issues/2025
+				suite.Equal(oldMintModuleBalanceAmount.Sub(expectedDistributed).Int64(), actualMintModuleBalance.Amount.Int64())
+
+				// Allocate to community pool when no addresses are provided.
+				if len(tc.recepientAddresses) == 0 {
+					suite.Equal(oldDeveloperVestingModuleBalanceAmount.Sub(expectedDistributed).Int64(), actualDeveloperVestingModuleBalanceAmount.Int64())
+					suite.Equal(oldCommunityPoolBalanceAmount.Add(expectedDistributed).Int64(), actualCommunityPoolModuleBalanceAmount.Int64())
+					return
+				}
+
+				// TODO: these should be equal, slightly off due to known rounding issues: https://github.com/osmosis-labs/osmosis/issues/1917
+				// suite.Equal(oldDeveloperVestingModuleBalanceAmount.Sub(expectedDistributed).Int64(), actualDeveloperVestingModuleBalanceAmount.Int64())
+
+				for i, weightedAddress := range tc.recepientAddresses {
+					address, err := sdk.AccAddressFromBech32(weightedAddress.Address)
+					suite.NoError(err)
+
+					// TODO: truncation should not occur: https://github.com/osmosis-labs/osmosis/issues/1917
+					expectedAllocation := expectedDistributed.ToDec().Mul(tc.recepientAddresses[i].Weight).TruncateInt()
+					actualDeveloperRewardsBalanceAmounts := bankKeeper.GetBalance(ctx, address, tc.mintedCoin.Denom).Amount
+
+					// Edge case. See testcases with this flag set to true for details.
+					if tc.expectSameAddresses {
+						suite.Equal(oldDeveloperRewardsBalanceAmounts[i].Add(expectedAllocation.Mul(sdk.NewInt(2))).Int64(), actualDeveloperRewardsBalanceAmounts.Int64())
+						return
+					}
+
+					suite.Equal(oldDeveloperRewardsBalanceAmounts[i].Add(expectedAllocation).Int64(), actualDeveloperRewardsBalanceAmounts.Int64())
+				}
 			})
 		})
 	}
diff --git a/x/txfees/keeper/hooks_test.go b/x/txfees/keeper/hooks_test.go
index 9995ea275..6b5f97b1e 100644
--- a/x/txfees/keeper/hooks_test.go
+++ b/x/txfees/keeper/hooks_test.go
@@ -64,7 +64,7 @@ func (suite *KeeperTestSuite) TestTxFeesAfterEpochEnd() {
 		},
 	}
 
-	var finalOutputAmount = sdk.NewInt(0)
+	finalOutputAmount := sdk.NewInt(0)
 
 	for _, tc := range tests {
 		tc := tc