diff --git a/.github/workflows/sims.yml b/.github/workflows/sims.yml deleted file mode 100644 index 38dfbbb3b..000000000 --- a/.github/workflows/sims.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Run short simulations - -on: - pull_request: - paths: ["**.go", "**.proto", "go.mod", "go.sum"] - -jobs: - install-runsim: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: 1.21 - cache: true - - uses: actions/cache@v3 - with: - path: ~/go/bin - key: ${{ runner.os }}-go-runsim-binary - - name: Install runsim - run: go install github.com/cosmos/tools/cmd/runsim@v1.0.0 - - test-sim-nondeterminism: - runs-on: ubuntu-latest - needs: [install-runsim] - steps: - - uses: actions/checkout@v4 - - uses: technote-space/get-diff-action@v6 - with: - SUFFIX_FILTER: | - **/**.go - go.mod - go.sum - - uses: actions/setup-go@v5 - with: - go-version: 1.21 - cache: true - if: env.GIT_DIFF - - uses: actions/cache@v3 - with: - path: ~/go/bin - key: ${{ runner.os }}-go-runsim-binary - if: env.GIT_DIFF - - name: test-sim-nondeterminism - run: | - make test-sim-nondeterminism - if: env.GIT_DIFF diff --git a/.github/workflows/simulation-tests.yml b/.github/workflows/simulation-tests.yml new file mode 100644 index 000000000..ccd345815 --- /dev/null +++ b/.github/workflows/simulation-tests.yml @@ -0,0 +1,63 @@ +name: Simulation tests + +on: + push: + branches: + # every push to default branch + - main + schedule: + # once per day + - cron: "0 0 * * *" + pull_request: + branches: + # every pull request to default branch + - main + +jobs: + test-sim-nondeterminism: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.21 + cache: true + - name: TestAppStateDeterminism + run: | + make test-sim-nondeterminism + + test-sim-default-genesis-fast: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.21 + cache: true + - name: TestFullAppSimulation + run: | + make test-sim-default-genesis-fast + + test-sim-import-export: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.21 + cache: true + - name: TestAppImportExport + run: | + make test-sim-import-export + + test-sim-after-import: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.21 + cache: true + - name: TestAppSimulationAfterImport + run: | + make test-sim-after-import \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aa3a6e25..0f3853288 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,7 +58,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#1686](https://github.com/NibiruChain/nibiru/pull/1686) - test(perp): add more tests for perp module msg server for DnR * [#1683](https://github.com/NibiruChain/nibiru/pull/1683) - feat(perp): Add `StartDnREpoch` to `AfterEpochEnd` hook * [#1680](https://github.com/NibiruChain/nibiru/pull/1680) - feat(perp): MsgShiftPegMultiplier, MsgShiftSwapInvariant. -* [#1680](https://github.com/NibiruChain/nibiru/pull/1680) - feat(perp): MsgShiftPegMultiplier, MsgShiftSwapInvariant. * [#1677](https://github.com/NibiruChain/nibiru/pull/1677) - fix(perp): make Gen_market set initial perp versions * [#1669](https://github.com/NibiruChain/nibiru/pull/1669) - feat(perp): add query to get collateral metadata * [#1663](https://github.com/NibiruChain/nibiru/pull/1663) - feat(perp): Add volume based rebates @@ -77,16 +76,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#1695](https://github.com/NibiruChain/nibiru/pull/1695) - feat(inflation): add events for inflation distribution * [#1695](https://github.com/NibiruChain/nibiru/pull/1695) - fix(sudo): Make blank sudoers root invalid at genesis time. * [#1710](https://github.com/NibiruChain/nibiru/pull/1710) - refactor(perp): Clean and organize module errors for x/perp -* [#1714](https://github.com/NibiruChain/nibiru/pull/1714) - ci(localnet.sh): Fix script, simplify, and test in CI. +* [#1714](https://github.com/NibiruChain/nibiru/pull/1714) - ci(localnet.sh): Fix script, simplify, and test in CI. * [#1719](https://github.com/NibiruChain/nibiru/pull/1719) - refactor(test): add is not mandatory interface to action * [#1723](https://github.com/NibiruChain/nibiru/pull/1723) - ci(e2e-wasm.yml): rm unused workflow +* [#1728](https://github.com/NibiruChain/nibiru/pull/1728) - test(devgas-cli): CLI tests for devgas txs +* [#1735](https://github.com/NibiruChain/nibiru/pull/1735) - test(sim): fix simulation tests ### Dependencies -- Bump `google.golang.org/grpc` from 1.59.0 to 1.60.0 ([#1720](https://github.com/NibiruChain/nibiru/pull/1720)) -- Bump `golang.org/x/crypto` from 0.15.0 to 0.17.0 ([#1724](https://github.com/NibiruChain/nibiru/pull/1724)) -- Bump `github.com/holiman/uint256` from 1.2.3 to 1.2.4 ([#1730](https://github.com/NibiruChain/nibiru/pull/1730)) -- Bump `github.com/dvsekhvalnov/jose2go` from 1.5.0 to 1.6.0 ([#1733](https://github.com/NibiruChain/nibiru/pull/1733)) +- Bump `github.com/prometheus/client_golang` from 1.17.0 to 1.18.0 ([#1750](https://github.com/NibiruChain/nibiru/pull/1750)) +* Bump `google.golang.org/grpc` from 1.59.0 to 1.60.0 ([#1720](https://github.com/NibiruChain/nibiru/pull/1720)) +* Bump `golang.org/x/crypto` from 0.15.0 to 0.17.0 ([#1724](https://github.com/NibiruChain/nibiru/pull/1724)) +* Bump `github.com/holiman/uint256` from 1.2.3 to 1.2.4 ([#1730](https://github.com/NibiruChain/nibiru/pull/1730)) +* Bump `github.com/dvsekhvalnov/jose2go` from 1.5.0 to 1.6.0 ([#1733](https://github.com/NibiruChain/nibiru/pull/1733)) * Bump `github.com/spf13/cast` from 1.5.1 to 1.6.0 ([#1689](https://github.com/NibiruChain/nibiru/pull/1689)) * Bump `cosmossdk.io/math` from 1.1.2 to 1.2.0 ([#1676](https://github.com/NibiruChain/nibiru/pull/1676)) * Bump `github.com/grpc-ecosystem/grpc-gateway/v2` from 2.18.0 to 2.18.1 ([#1675](https://github.com/NibiruChain/nibiru/pull/1675)) @@ -94,7 +96,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Bump `golang` from 1.19 to 1.21 ([#1698](https://github.com/NibiruChain/nibiru/pull/1698)) * [#1678](https://github.com/NibiruChain/nibiru/pull/1678) - chore(deps): collections to v0.4.0 for math.Int value encoder - ## [v1.1.0] - 2023-11-20 * [[Release Link](https://github.com/NibiruChain/nibiru/releases/tag/v1.1.0)] diff --git a/app/app.go b/app/app.go index 3e28de638..943ecc4d8 100644 --- a/app/app.go +++ b/app/app.go @@ -90,7 +90,7 @@ type NibiruApp struct { AppKeepers // embed all module keepers // the module manager - mm *module.Manager + ModuleManager *module.Manager // simulation manager sm *module.SimulationManager @@ -179,7 +179,7 @@ func NewNibiruApp( // add test gRPC service for testing gRPC queries in isolation testdata.RegisterQueryServer(app.GRPCQueryRouter(), testdata.QueryImpl{}) - app.InitSimulationManager(app.appCodec) + app.initSimulationManager(app.appCodec) // initialize stores app.MountKVStores(keys) @@ -251,12 +251,12 @@ func (app *NibiruApp) Name() string { return app.BaseApp.Name() } // BeginBlocker application updates every begin block func (app *NibiruApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { - return app.mm.BeginBlock(ctx, req) + return app.ModuleManager.BeginBlock(ctx, req) } // EndBlocker application updates every end block func (app *NibiruApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - return app.mm.EndBlock(ctx, req) + return app.ModuleManager.EndBlock(ctx, req) } // InitChainer application update at chain initialization @@ -265,8 +265,8 @@ func (app *NibiruApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) ab if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { panic(err) } - app.upgradeKeeper.SetModuleVersionMap(ctx, app.mm.GetVersionMap()) - return app.mm.InitGenesis(ctx, app.appCodec, genesisState) + app.upgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()) + return app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState) } // LoadHeight loads a particular height diff --git a/app/export.go b/app/export.go index 37fe4efd5..7a9a5ed6a 100644 --- a/app/export.go +++ b/app/export.go @@ -29,7 +29,7 @@ func (app *NibiruApp) ExportAppStateAndValidators( app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) } - genState := app.mm.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) + genState := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) appState, err := json.MarshalIndent(genState, "", " ") if err != nil { return servertypes.ExportedApp{}, err diff --git a/app/keepers.go b/app/keepers.go index 874321829..efefdc215 100644 --- a/app/keepers.go +++ b/app/keepers.go @@ -707,23 +707,23 @@ func (app *NibiruApp) initModuleManager( encodingConfig EncodingConfig, skipGenesisInvariants bool, ) { - app.mm = module.NewManager( + app.ModuleManager = module.NewManager( app.initAppModules(encodingConfig, skipGenesisInvariants)..., ) orderedModules := orderedModuleNames() - app.mm.SetOrderBeginBlockers(orderedModules...) - app.mm.SetOrderEndBlockers(orderedModules...) - app.mm.SetOrderInitGenesis(orderedModules...) - app.mm.SetOrderExportGenesis(orderedModules...) + app.ModuleManager.SetOrderBeginBlockers(orderedModules...) + app.ModuleManager.SetOrderEndBlockers(orderedModules...) + app.ModuleManager.SetOrderInitGenesis(orderedModules...) + app.ModuleManager.SetOrderExportGenesis(orderedModules...) // Uncomment if you want to set a custom migration order here. // app.mm.SetOrderMigrations(custom order) - app.mm.RegisterInvariants(&app.crisisKeeper) + app.ModuleManager.RegisterInvariants(&app.crisisKeeper) app.configurator = module.NewConfigurator( app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) - app.mm.RegisterServices(app.configurator) + app.ModuleManager.RegisterServices(app.configurator) // see https://github.com/cosmos/cosmos-sdk/blob/666c345ad23ddda9523cc5cd1b71187d91c26f34/simapp/upgrades.go#L35-L57 for _, subspace := range app.paramsKeeper.GetSubspaces() { @@ -849,34 +849,13 @@ func initParamsKeeper( return paramsKeeper } -// TODO: Simulation manager -func (app *NibiruApp) InitSimulationManager( +func (app *NibiruApp) initSimulationManager( appCodec codec.Codec, ) { - // // create the simulation manager and define the order of the modules for deterministic simulations - // // - // // NOTE: this is not required apps that don't use the simulator for fuzz testing - // // transactions - // epochsModule := epochs.NewAppModule(appCodec, app.EpochsKeeper) - // app.sm = module.NewSimulationManager( - // auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts), - // bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), - // feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), - // gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper), - // staking.NewAppModule(appCodec, app.stakingKeeper, app.AccountKeeper, app.BankKeeper), - // distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.stakingKeeper), - // slashing.NewAppModule(appCodec, app.slashingKeeper, app.AccountKeeper, app.BankKeeper, app.stakingKeeper), - // params.NewAppModule(app.paramsKeeper), - // authzmodule.NewAppModule(appCodec, app.authzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), - // // native x/ - // epochsModule, - // // ibc - // capability.NewAppModule(appCodec, *app.capabilityKeeper), - // evidence.NewAppModule(app.evidenceKeeper), - // ibc.NewAppModule(app.ibcKeeper), - // ibctransfer.NewAppModule(app.transferKeeper), - // ibcfee.NewAppModule(app.ibcFeeKeeper), - // ) - // - // app.sm.RegisterStoreDecoders() + overrideModules := map[string]module.AppModuleSimulation{ + authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + } + app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules) + + app.sm.RegisterStoreDecoders() } diff --git a/app/upgrades.go b/app/upgrades.go index 8487b5f22..26093c521 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -20,7 +20,7 @@ func (app *NibiruApp) setupUpgrades() { func (app *NibiruApp) setUpgradeHandlers() { for _, u := range Upgrades { - app.upgradeKeeper.SetUpgradeHandler(u.UpgradeName, u.CreateUpgradeHandler(app.mm, app.configurator)) + app.upgradeKeeper.SetUpgradeHandler(u.UpgradeName, u.CreateUpgradeHandler(app.ModuleManager, app.configurator)) } } diff --git a/contrib/make/simulation.mk b/contrib/make/simulation.mk index 31836c6bf..20eb2151c 100644 --- a/contrib/make/simulation.mk +++ b/contrib/make/simulation.mk @@ -1,43 +1,51 @@ -BINDIR = $(GOPATH)/bin -RUNSIM = $(BINDIR)/runsim SIMAPP = ./simapp -.PHONY: runsim -runsim: $(RUNSIM) -$(RUNSIM): - @echo "Installing runsim..." - @(cd /tmp && go install github.com/cosmos/tools/cmd/runsim@v1.0.0) - .PHONY: test-sim-nondeterminism test-sim-nondeterminism: @echo "Running non-determinism test..." - @go test -mod=readonly $(SIMAPP) -run TestAppStateDeterminism -Enabled=true \ - -NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h + @go test -mod=readonly -v $(SIMAPP) \ + -run TestAppStateDeterminism \ + -Enabled=true \ + -Params=params.json \ + -NumBlocks=100 \ + -BlockSize=200 \ + -Commit=true \ + -Period=0 \ + -Verbose=true .PHONY: test-sim-default-genesis-fast test-sim-default-genesis-fast: @echo "Running default genesis simulation..." - @go test -mod=readonly $(SIMAPP) -run TestFullAppSimulation \ - -Enabled=true -NumBlocks=100 -BlockSize=200 -Commit=true -Seed=99 -Period=5 -v - -.PHONY: test-sim-custom-genesis-multi-seed -test-sim-custom-genesis-multi-seed: runsim - @echo "Running multi-seed custom genesis simulation..." - @$(RUNSIM) -SimAppPkg=$(SIMAPP) -ExitOnFail 400 5 TestFullAppSimulation - -.PHONY: test-sim-multi-seed-long -test-sim-multi-seed-long: runsim - @echo "Running long multi-seed application simulation. This may take awhile!" - @$(RUNSIM) -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 500 50 TestFullAppSimulation + @go test -mod=readonly -v $(SIMAPP) \ + -run TestFullAppSimulation \ + -Params=params.json \ + -Enabled=true \ + -NumBlocks=100 \ + -BlockSize=200 \ + -Commit=true \ + -Seed=99 \ + -Period=0 -.PHONY: test-sim-multi-seed-short -test-sim-multi-seed-short: runsim - @echo "Running short multi-seed application simulation. This may take awhile!" - @$(RUNSIM) -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 50 10 TestFullAppSimulation +.PHONY: test-sim-import-export +test-sim-import-export: + @echo "Running application import/export simulation. This may take several minutes..." + @go test -mod=readonly -v $(SIMAPP) \ + -run TestAppImportExport \ + -Params=params.json \ + -Enabled=true \ + -NumBlocks=100 \ + -Commit=true \ + -Seed=99 \ + -Period=5 -.PHONY: test-sim-benchmark-invariants -test-sim-benchmark-invariants: - @echo "Running simulation invariant benchmarks..." - @go test -mod=readonly $(SIMAPP) -benchmem -bench=BenchmarkInvariants -run=^$ \ - -Enabled=true -NumBlocks=1000 -BlockSize=200 \ - -Period=1 -Commit=true -Seed=57 -v -timeout 24h \ No newline at end of file +.PHONY: test-sim-after-import +test-sim-after-import: + @echo "Running application simulation-after-import. This may take several minutes..." + @go test -mod=readonly -v $(SIMAPP) \ + -run TestAppSimulationAfterImport \ + -Params=params.json \ + -Enabled=true \ + -NumBlocks=50 \ + -Commit=true \ + -Seed=99 \ + -Period=5 diff --git a/go.mod b/go.mod index b76d57b30..dc7dc715d 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 github.com/holiman/uint256 v1.2.4 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.17.0 + github.com/prometheus/client_golang v1.18.0 github.com/rakyll/statik v0.1.7 github.com/spf13/cast v1.6.0 github.com/spf13/cobra v1.8.0 @@ -135,7 +135,7 @@ require ( github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -146,9 +146,9 @@ require ( github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect - github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.11.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rs/cors v1.8.3 // indirect diff --git a/go.sum b/go.sum index 83f9f266b..d5737d783 100644 --- a/go.sum +++ b/go.sum @@ -919,8 +919,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 h1:QRUSJEgZn2Snx0EmT/QLXibWjSUDjKWvXIT19NBVp94= @@ -1044,16 +1044,16 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -1062,16 +1062,16 @@ github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt2 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= diff --git a/simapp/README.md b/simapp/README.md new file mode 100644 index 000000000..edc956ea5 --- /dev/null +++ b/simapp/README.md @@ -0,0 +1,59 @@ +# Simulation Tests + +This directory contains the simulation tests for the `simapp` module + +## Test Cases + +### Non-Determinism + +```sh +make test-sim-non-determinism +``` + +This test case checks that the simulation is deterministic. It does so by +running the simulation twice with the same seed and comparing the resulting +state. If the simulation is deterministic, the resulting state should be the +same. + +### Full App + +```sh +make test-sim-default-genesis-fast +``` + +This test case runs the simulation with the default genesis file. It checks that +the simulation does not panic and that the resulting state is valid. + +### Import/Export + +```sh +make test-sim-import-export +``` + +This test case runs the simulation with the default genesis file. It checks that +the simulation does not panic and that the resulting state is valid. It then +exports the state to a file and imports it back. It checks that the imported +state is the same as the exported state. + +### Simulation After Import + +```sh +make test-sim-after-import +``` + +This test case runs the simulation with the default genesis file. It checks that +the simulation does not panic and that the resulting state is valid. It then +exports the state to a file and imports it back. It checks that the imported +state is the same as the exported state. It then runs the simulation again with +the imported state. It checks that the simulation does not panic and that the +resulting state is valid. + +## Params + +A `params.json` file is included that sets the operation weights for +`CreateValidator` and `EditValidator` to zero. It's a hack to make the +simulation tests pass. The random commission rates sometimes halt the simulation +because the max commission rate is set to 0.25 in the AnteHandler, but sometimes +the random commission rate is higher than that. The random commission is set by +the cosmos-sdk x/staking module simulation operations, so we have no control +over injecting a manual value. diff --git a/simapp/params.json b/simapp/params.json new file mode 100644 index 000000000..67f8d113c --- /dev/null +++ b/simapp/params.json @@ -0,0 +1,4 @@ +{ + "op_weight_msg_create_validator": 0, + "op_weight_msg_edit_validator": 0 +} \ No newline at end of file diff --git a/simapp/sim_test.go b/simapp/sim_test.go index 5454d709d..a1e1bc4ce 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -5,128 +5,139 @@ import ( "fmt" "math/rand" "os" + "runtime/debug" + "strings" "testing" - "github.com/cosmos/ibc-go/v7/testing/simapp" - dbm "github.com/cometbft/cometbft-db" - helpers "github.com/cosmos/cosmos-sdk/testutil/sims" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/libs/log" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/server" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/cosmos/cosmos-sdk/x/simulation" simcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" "github.com/NibiruChain/nibiru/app" - appsim "github.com/NibiruChain/nibiru/app/sim" - "github.com/NibiruChain/nibiru/x/common/testutil" - "github.com/NibiruChain/nibiru/x/common/testutil/testapp" + devgastypes "github.com/NibiruChain/nibiru/x/devgas/v1/types" + epochstypes "github.com/NibiruChain/nibiru/x/epochs/types" + inflationtypes "github.com/NibiruChain/nibiru/x/inflation/types" + oracletypes "github.com/NibiruChain/nibiru/x/oracle/types" + perptypes "github.com/NibiruChain/nibiru/x/perp/v2/types" + spottypes "github.com/NibiruChain/nibiru/x/spot/types" + sudotypes "github.com/NibiruChain/nibiru/x/sudo/types" + tokenfactorytypes "github.com/NibiruChain/nibiru/x/tokenfactory/types" ) // SimAppChainID hardcoded chainID for simulation const SimAppChainID = "simulation-app" -type SimulationTestSuite struct { - suite.Suite -} - -func TestSimulationTestSuite(t *testing.T) { - suite.Run(t, new(SimulationTestSuite)) -} - -var _ suite.SetupTestSuite = (*SimulationTestSuite)(nil) - func init() { // We call GetSimulatorFlags here in order to set the value for - // 'simapp.FlagEnabledValue', which enables simulations - appsim.GetSimulatorFlags() + // 'simcli.FlagEnabledValue', which enables simulations + simcli.GetSimulatorFlags() } -// SetupTest: Runs before every test in the suite. -func (s *SimulationTestSuite) SetupTest() { - testutil.BeforeIntegrationSuite(s.T()) - if !simapp.FlagEnabledValue { - s.T().Skip("skipping application simulation") - } +type StoreKeysPrefixes struct { + A storetypes.StoreKey + B storetypes.StoreKey + Prefixes [][]byte } -func (s *SimulationTestSuite) TestFullAppSimulation() { - t := s.T() +func TestFullAppSimulation(t *testing.T) { config := simcli.NewConfigFromFlags() config.ChainID = SimAppChainID - db, dir, _, skip, err := helpers.SetupSimulation( - config, - "goleveldb-app-sim", - "Simulation", - simcli.FlagVerboseValue, simcli.FlagEnabledValue, - ) + db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "goleveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue) if skip { t.Skip("skipping application simulation") } require.NoError(t, err, "simulation setup failed") defer func() { - db.Close() - err = os.RemoveAll(dir) - if err != nil { - t.Fatal(err) - } + require.NoError(t, db.Close()) + require.NoError(t, os.RemoveAll(dir)) }() + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = app.DefaultNodeHome + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue + encoding := app.MakeEncodingConfig() - app := testapp.NewNibiruTestApp(app.NewDefaultGenesisState(encoding.Marshaler)) + app := app.NewNibiruApp(logger, db, nil, true, encoding, appOptions, baseapp.SetChainID(SimAppChainID)) + require.Equal(t, "Nibiru", app.Name()) + appCodec := app.AppCodec() - // Run randomized simulation: + // run randomized simulation _, simParams, simErr := simulation.SimulateFromSeed( - /* tb */ t, - /* w */ os.Stdout, - /* app */ app.BaseApp, - /* appStateFn */ AppStateFn(app.AppCodec(), app.SimulationManager()), - /* randAccFn */ simulationtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 - /* ops */ helpers.SimulationOperations(app, app.AppCodec(), config), // Run all registered operations - /* blockedAddrs */ app.ModuleAccountAddrs(), - /* config */ config, - /* cdc */ app.AppCodec(), + t, + os.Stdout, + app.BaseApp, + AppStateFn(appCodec, app.SimulationManager()), + simtypes.RandomAccounts, + simtestutil.SimulationOperations(app, appCodec, config), + app.ModuleAccountAddrs(), + config, + appCodec, ) // export state and simParams before the simulation error is checked - if err = helpers.CheckExportSimulation(app, config, simParams); err != nil { - t.Fatal(err) - } - - if simErr != nil { - t.Fatal(simErr) - } + err = simtestutil.CheckExportSimulation(app, config, simParams) + require.NoError(t, err) + require.NoError(t, simErr) if config.Commit { - simapp.PrintStats(db) + simtestutil.PrintStats(db) } } -func (s *SimulationTestSuite) TestAppStateDeterminism() { - t := s.T() - - encoding := app.MakeEncodingConfig() +// Tests that the app state hash is deterministic when the operations are run +func TestAppStateDeterminism(t *testing.T) { + if !simcli.FlagEnabledValue { + t.Skip("skipping application simulation") + } - config := simapp.NewConfigFromFlags() + config := simcli.NewConfigFromFlags() config.InitialBlockHeight = 1 config.ExportParamsPath = "" config.OnOperation = false config.AllInvariants = false config.ChainID = SimAppChainID - numSeeds := 3 numTimesToRunPerSeed := 5 + appHashList := make([]json.RawMessage, numTimesToRunPerSeed) + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = app.DefaultNodeHome + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue for i := 0; i < numSeeds; i++ { config.Seed = rand.Int63() for j := 0; j < numTimesToRunPerSeed; j++ { db := dbm.NewMemDB() - app := testapp.NewNibiruTestApp(app.NewDefaultGenesisState(encoding.Marshaler)) + logger := log.NewNopLogger() + encoding := app.MakeEncodingConfig() + + app := app.NewNibiruApp(logger, db, nil, true, encoding, appOptions, baseapp.SetChainID(SimAppChainID)) + appCodec := app.AppCodec() fmt.Printf( "running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n", @@ -137,17 +148,17 @@ func (s *SimulationTestSuite) TestAppStateDeterminism() { t, os.Stdout, app.BaseApp, - AppStateFn(app.AppCodec(), app.SimulationManager()), - simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 - helpers.SimulationOperations(app, app.AppCodec(), config), + AppStateFn(appCodec, app.SimulationManager()), + simtypes.RandomAccounts, + simtestutil.SimulationOperations(app, appCodec, config), app.ModuleAccountAddrs(), config, - app.AppCodec(), + appCodec, ) require.NoError(t, err) if config.Commit { - simapp.PrintStats(db) + simtestutil.PrintStats(db) } appHash := app.LastCommitID().Hash @@ -162,3 +173,217 @@ func (s *SimulationTestSuite) TestAppStateDeterminism() { } } } + +func TestAppImportExport(t *testing.T) { + config := simcli.NewConfigFromFlags() + config.ChainID = SimAppChainID + + db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "goleveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue) + if skip { + t.Skip("skipping application import/export simulation") + } + require.NoError(t, err, "simulation setup failed") + + defer func() { + require.NoError(t, db.Close()) + require.NoError(t, os.RemoveAll(dir)) + }() + + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = app.DefaultNodeHome + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue + + encoding := app.MakeEncodingConfig() + oldApp := app.NewNibiruApp(logger, db, nil, true, encoding, appOptions, baseapp.SetChainID(SimAppChainID)) + require.Equal(t, "Nibiru", oldApp.Name()) + appCodec := oldApp.AppCodec() + + // Run randomized simulation + _, simParams, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + oldApp.BaseApp, + AppStateFn(appCodec, oldApp.SimulationManager()), + simtypes.RandomAccounts, + simtestutil.SimulationOperations(oldApp, oldApp.AppCodec(), config), + oldApp.ModuleAccountAddrs(), + config, + oldApp.AppCodec(), + ) + + // export state and simParams before the simulation error is checked + err = simtestutil.CheckExportSimulation(oldApp, config, simParams) + require.NoError(t, err) + require.NoError(t, simErr) + + if config.Commit { + simtestutil.PrintStats(db) + } + + fmt.Printf("exporting genesis...\n") + + exported, err := oldApp.ExportAppStateAndValidators(false, []string{}, []string{}) + require.NoError(t, err) + + fmt.Printf("importing genesis...\n") + + newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "goleveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue) + require.NoError(t, err, "simulation setup failed") + + defer func() { + require.NoError(t, newDB.Close()) + require.NoError(t, os.RemoveAll(newDir)) + }() + + newApp := app.NewNibiruApp(log.NewNopLogger(), newDB, nil, true, encoding, appOptions, baseapp.SetChainID(SimAppChainID)) + require.Equal(t, "Nibiru", newApp.Name()) + + var genesisState app.GenesisState + err = json.Unmarshal(exported.AppState, &genesisState) + require.NoError(t, err) + + defer func() { + if r := recover(); r != nil { + err := fmt.Sprintf("%v", r) + if !strings.Contains(err, "validator set is empty after InitGenesis") { + panic(r) + } + logger.Info("Skipping simulation as all validators have been unbonded") + logger.Info("err", err, "stacktrace", string(debug.Stack())) + } + }() + + ctxA := oldApp.NewContext(true, tmproto.Header{Height: oldApp.LastBlockHeight()}) + ctxB := newApp.NewContext(true, tmproto.Header{Height: oldApp.LastBlockHeight()}) + newApp.ModuleManager.InitGenesis(ctxB, oldApp.AppCodec(), genesisState) + newApp.StoreConsensusParams(ctxB, exported.ConsensusParams) + + fmt.Printf("comparing stores...\n") + + storeKeysPrefixes := []StoreKeysPrefixes{ + {oldApp.GetKey(authtypes.StoreKey), newApp.GetKey(authtypes.StoreKey), [][]byte{}}, + { + oldApp.GetKey(stakingtypes.StoreKey), newApp.GetKey(stakingtypes.StoreKey), + [][]byte{ + stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey, + stakingtypes.HistoricalInfoKey, stakingtypes.UnbondingIDKey, stakingtypes.UnbondingIndexKey, stakingtypes.UnbondingTypeKey, stakingtypes.ValidatorUpdatesKey, + }, + }, // ordering may change but it doesn't matter + {oldApp.GetKey(slashingtypes.StoreKey), newApp.GetKey(slashingtypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(minttypes.StoreKey), newApp.GetKey(minttypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(distrtypes.StoreKey), newApp.GetKey(distrtypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(banktypes.StoreKey), newApp.GetKey(banktypes.StoreKey), [][]byte{banktypes.BalancesPrefix}}, + {oldApp.GetKey(paramtypes.StoreKey), newApp.GetKey(paramtypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(govtypes.StoreKey), newApp.GetKey(govtypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(evidencetypes.StoreKey), newApp.GetKey(evidencetypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(capabilitytypes.StoreKey), newApp.GetKey(capabilitytypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(authzkeeper.StoreKey), newApp.GetKey(authzkeeper.StoreKey), [][]byte{authzkeeper.GrantKey, authzkeeper.GrantQueuePrefix}}, + {oldApp.GetKey(devgastypes.StoreKey), newApp.GetKey(devgastypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(epochstypes.StoreKey), newApp.GetKey(epochstypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(inflationtypes.StoreKey), newApp.GetKey(inflationtypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(oracletypes.StoreKey), newApp.GetKey(oracletypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(perptypes.StoreKey), newApp.GetKey(perptypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(spottypes.StoreKey), newApp.GetKey(spottypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(sudotypes.StoreKey), newApp.GetKey(sudotypes.StoreKey), [][]byte{}}, + {oldApp.GetKey(tokenfactorytypes.StoreKey), newApp.GetKey(tokenfactorytypes.StoreKey), [][]byte{}}, + } + + for _, skp := range storeKeysPrefixes { + storeA := ctxA.KVStore(skp.A) + storeB := ctxB.KVStore(skp.B) + + failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes) + require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") + + fmt.Printf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B) + require.Equal(t, 0, len(failedKVAs), simtestutil.GetSimulationLog(skp.A.Name(), oldApp.SimulationManager().StoreDecoders, failedKVAs, failedKVBs)) + } +} + +func TestAppSimulationAfterImport(t *testing.T) { + config := simcli.NewConfigFromFlags() + config.ChainID = SimAppChainID + + db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "goleveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue) + if skip { + t.Skip("skipping application simulation after import") + } + require.NoError(t, err, "simulation setup failed") + + defer func() { + require.NoError(t, db.Close()) + require.NoError(t, os.RemoveAll(dir)) + }() + + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = app.DefaultNodeHome + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue + + encoding := app.MakeEncodingConfig() + oldApp := app.NewNibiruApp(logger, db, nil, true, encoding, appOptions, baseapp.SetChainID(SimAppChainID)) + require.Equal(t, "Nibiru", oldApp.Name()) + appCodec := oldApp.AppCodec() + + // Run randomized simulation + stopEarly, simParams, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + oldApp.BaseApp, + AppStateFn(appCodec, oldApp.SimulationManager()), + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 + simtestutil.SimulationOperations(oldApp, appCodec, config), + oldApp.ModuleAccountAddrs(), + config, + appCodec, + ) + + // export state and simParams before the simulation error is checked + err = simtestutil.CheckExportSimulation(oldApp, config, simParams) + require.NoError(t, err) + require.NoError(t, simErr) + + if config.Commit { + simtestutil.PrintStats(db) + } + + if stopEarly { + fmt.Println("can't export or import a zero-validator genesis, exiting test...") + return + } + + fmt.Printf("exporting genesis...\n") + + exported, err := oldApp.ExportAppStateAndValidators(true, []string{}, []string{}) + require.NoError(t, err) + + fmt.Printf("importing genesis...\n") + + newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "leveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue) + require.NoError(t, err, "simulation setup failed") + + defer func() { + require.NoError(t, newDB.Close()) + require.NoError(t, os.RemoveAll(newDir)) + }() + + newApp := app.NewNibiruApp(log.NewNopLogger(), newDB, nil, true, encoding, appOptions, baseapp.SetChainID(SimAppChainID)) + require.Equal(t, "Nibiru", newApp.Name()) + + newApp.InitChain(abci.RequestInitChain{ + ChainId: SimAppChainID, + AppStateBytes: exported.AppState, + }) + + _, _, err = simulation.SimulateFromSeed( + t, + os.Stdout, + newApp.BaseApp, + AppStateFn(appCodec, newApp.SimulationManager()), + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 + simtestutil.SimulationOperations(newApp, newApp.AppCodec(), config), + newApp.ModuleAccountAddrs(), + config, + oldApp.AppCodec(), + ) + require.NoError(t, err) +} diff --git a/simapp/state_test.go b/simapp/state_test.go index 869e400c0..948dbc68e 100644 --- a/simapp/state_test.go +++ b/simapp/state_test.go @@ -64,6 +64,7 @@ func AppStateFn(cdc codec.JSONCodec, simManager *module.SimulationManager) simty if err != nil { panic(err) } + appState, simAccs = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams) default: diff --git a/x/README.md b/x/README.md index 63431e1bb..75ea21f45 100644 --- a/x/README.md +++ b/x/README.md @@ -1,21 +1,3 @@ # Nibiru Modules -| Module | Active? | Description | -| ------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [common][code-x-common] | ✔️ | Holds helper and utility functions to be utilized by other `x/` modules. | -| [epochs][code-x-epochs] | ✔️ | Often in the SDK, we would like to run certain code every-so often. The purpose of `epochs` module is to allow other modules to set that they would like to be signaled once every period. So another module can specify it wants to execute code once a week, starting at UTC-time = x. `epochs` creates a generalized epoch interface to other modules so that they can easily be signalled upon such events. | -| [oracle][code-x-oracle] | ✔️ | Handles the posting of an up-to-date and accurate feed of exchange rates from the validators. | -| [perp][code-x-perp] | ✔️ | Powers the Nibi-Perps exchange. This module enables traders to open long and short leveraged positions and houses all of the PnL calculation and liquidation logic. | -| [spot][code-x-spot] | ✔️ | Responsible for creating, joining, and exiting liquidity pools. It also allows users to swap between two assets in an existing pool. It's a fully functional AMM. | -| [stablecoin][code-x-stablecoin] | ⭕️ | Resonsible for handling mint and redeem transactions with NUSD. | -| [testutil][code-x-testutil] | ✔️ | Helper functions for unit and integration tests. | -| [wasm][code-x-wasm] | ✔️ | Implements the execution environment for [WebAssembly (WASM) smart contracts](https://book.cosmwasm.com/). | - -[code-x-common]: https://github.com/NibiruChain/nibiru/tree/master/x/common -[code-x-epochs]: https://github.com/NibiruChain/nibiru/tree/master/x/epochs -[code-x-oracle]: https://github.com/NibiruChain/nibiru/tree/master/x/oracle -[code-x-perp]: https://github.com/NibiruChain/nibiru/tree/master/x/perp -[code-x-spot]: https://github.com/NibiruChain/nibiru/tree/master/x/spot -[code-x-stablecoin]: https://github.com/NibiruChain/nibiru/tree/master/x/stablecoin -[code-x-testutil]: https://github.com/NibiruChain/nibiru/tree/master/x/testutil -[code-x-wasm]: https://github.com/NibiruChain/nibiru/tree/master/x/wasm \ No newline at end of file +See [BlockChain Modules - Nibiru Docs](https://nibiru.fi/docs/dev/x/) diff --git a/x/common/testutil/testapp/testapp.go b/x/common/testutil/testapp/testapp.go index 20425a570..3d5f1f989 100644 --- a/x/common/testutil/testapp/testapp.go +++ b/x/common/testutil/testapp/testapp.go @@ -8,6 +8,7 @@ import ( abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/libs/log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" @@ -103,7 +104,7 @@ func NewNibiruTestAppAndContextAtTime(startTime time.Time) (*app.NibiruApp, sdk. // NewNibiruTestApp initializes a chain with the given genesis state to // creates an application instance ('app.NibiruApp'). This app uses an // in-memory database ('tmdb.MemDB') and has logging disabled. -func NewNibiruTestApp(gen app.GenesisState) *app.NibiruApp { +func NewNibiruTestApp(gen app.GenesisState, baseAppOptions ...func(*baseapp.BaseApp)) *app.NibiruApp { db := tmdb.NewMemDB() logger := log.NewNopLogger() @@ -117,6 +118,7 @@ func NewNibiruTestApp(gen app.GenesisState) *app.NibiruApp { /*loadLatest=*/ true, encoding, /*appOpts=*/ sims.EmptyAppOptions{}, + baseAppOptions..., ) gen, err := GenesisStateWithSingleValidator(encoding.Marshaler, gen) diff --git a/x/devgas/v1/client/cli/cli_test.go b/x/devgas/v1/client/cli/cli_test.go new file mode 100644 index 000000000..e2209046e --- /dev/null +++ b/x/devgas/v1/client/cli/cli_test.go @@ -0,0 +1,228 @@ +package cli_test + +import ( + "bytes" + "context" + "fmt" + "io" + "testing" + + abci "github.com/cometbft/cometbft/abci/types" + sdktestutil "github.com/cosmos/cosmos-sdk/testutil" + "github.com/stretchr/testify/suite" + + rpcclientmock "github.com/cometbft/cometbft/rpc/client/mock" + sdkclient "github.com/cosmos/cosmos-sdk/client" + sdktestutilcli "github.com/cosmos/cosmos-sdk/testutil/cli" + sdk "github.com/cosmos/cosmos-sdk/types" + testutilmod "github.com/cosmos/cosmos-sdk/types/module/testutil" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" + + "github.com/NibiruChain/nibiru/x/common/testutil" + devgas "github.com/NibiruChain/nibiru/x/devgas/v1" + "github.com/NibiruChain/nibiru/x/devgas/v1/client/cli" +) + +// CLITestSuite: Tests all tx commands for the module. +type CLITestSuite struct { + suite.Suite + + keyring keyring.Keyring + encCfg testutilmod.TestEncodingConfig + baseCtx sdkclient.Context + clientCtx sdkclient.Context + + testAcc sdktestutil.TestAccount +} + +func TestCLITestSuite(t *testing.T) { + suite.Run(t, new(CLITestSuite)) +} + +// Runs once before the entire test suite. +func (s *CLITestSuite) SetupSuite() { + s.encCfg = testutilmod.MakeTestEncodingConfig(devgas.AppModuleBasic{}) + s.keyring = keyring.NewInMemory(s.encCfg.Codec) + s.baseCtx = sdkclient.Context{}. + WithKeyring(s.keyring). + WithTxConfig(s.encCfg.TxConfig). + WithCodec(s.encCfg.Codec). + WithClient(sdktestutilcli.MockTendermintRPC{Client: rpcclientmock.Client{}}). + WithAccountRetriever(sdkclient.MockAccountRetriever{}). + WithOutput(io.Discard). + WithChainID("test-chain") + + var outBuf bytes.Buffer + ctxGen := func() sdkclient.Context { + bz, _ := s.encCfg.Codec.Marshal(&sdk.TxResponse{}) + c := sdktestutilcli.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + } + s.clientCtx = ctxGen().WithOutput(&outBuf) + + testAccs := sdktestutil.CreateKeyringAccounts(s.T(), s.keyring, 1) + s.testAcc = testAccs[0] +} + +// Flags for broadcasting transactions +func commonTxArgs() []string { + return []string{ + "--yes=true", // skip confirmation + "--broadcast-mode=sync", + "--fees=1unibi", + "--chain-id=test-chain", + } +} + +type TestCase struct { + name string + args []string + extraArgs []string + wantErr string +} + +func (tc TestCase) NewCtx(s *CLITestSuite) sdkclient.Context { + return s.baseCtx +} + +func (tc TestCase) Run(s *CLITestSuite) { + s.Run(tc.name, func() { + ctx := svrcmd.CreateExecuteContext(context.Background()) + + cmd := cli.NewTxCmd() + cmd.SetContext(ctx) + args := append(tc.args, commonTxArgs()...) + cmd.SetArgs(append(args, tc.extraArgs...)) + + s.Require().NoError(sdkclient.SetCmdClientContextHandler(tc.NewCtx(s), cmd)) + + err := cmd.Execute() + if tc.wantErr != "" { + s.Require().Error(err) + s.ErrorContains(err, tc.wantErr) + return + } + s.Require().NoError(err) + }) +} + +func (s *CLITestSuite) TestCmdRegisterFeeShare() { + _, addrs := testutil.PrivKeyAddressPairs(3) + + testCases := []TestCase{ + { + name: "happy path: devgas register", + args: []string{"register", addrs[0].String(), addrs[1].String()}, + extraArgs: []string{fmt.Sprintf("--from=%s", s.testAcc.Address)}, + wantErr: "", + }, + { + name: "sad: fee payer", + args: []string{"register", addrs[0].String(), addrs[1].String()}, + extraArgs: []string{ + fmt.Sprintf("--from=%s", s.testAcc.Address), + fmt.Sprintf("--fee-payer=%s", "invalid-fee-payer"), + }, + wantErr: "decoding bech32 failed", + }, + { + name: "sad: contract addr", + args: []string{"register", "sadcontract", addrs[1].String()}, + extraArgs: []string{ + fmt.Sprintf("--from=%s", s.testAcc.Address), + }, + wantErr: "invalid contract address", + }, + { + name: "sad: withdraw addr", + args: []string{"register", addrs[0].String(), "sadwithdraw"}, + extraArgs: []string{ + fmt.Sprintf("--from=%s", s.testAcc.Address), + }, + wantErr: "invalid withdraw address", + }, + } + + for _, tc := range testCases { + tc.Run(s) + } +} + +func (s *CLITestSuite) TestCmdCancelFeeShare() { + _, addrs := testutil.PrivKeyAddressPairs(1) + testCases := []TestCase{ + { + name: "happy path: devgas cancel", + args: []string{"cancel", addrs[0].String()}, + extraArgs: []string{fmt.Sprintf("--from=%s", s.testAcc.Address)}, + wantErr: "", + }, + { + name: "sad: fee payer", + args: []string{"cancel", addrs[0].String()}, + extraArgs: []string{ + fmt.Sprintf("--from=%s", s.testAcc.Address), + fmt.Sprintf("--fee-payer=%s", "invalid-fee-payer"), + }, + wantErr: "decoding bech32 failed", + }, + { + name: "sad: contract addr", + args: []string{"cancel", "sadcontract"}, + extraArgs: []string{ + fmt.Sprintf("--from=%s", s.testAcc.Address), + }, + wantErr: "invalid deployer address", + }, + } + + for _, tc := range testCases { + tc.Run(s) + } +} + +func (s *CLITestSuite) TestCmdUpdateFeeShare() { + _, addrs := testutil.PrivKeyAddressPairs(3) + + testCases := []TestCase{ + { + name: "happy path: devgas update", + args: []string{"update", addrs[0].String(), addrs[1].String()}, + extraArgs: []string{fmt.Sprintf("--from=%s", s.testAcc.Address)}, + wantErr: "", + }, + { + name: "sad: fee payer", + args: []string{"update", addrs[0].String(), addrs[1].String()}, + extraArgs: []string{ + fmt.Sprintf("--from=%s", s.testAcc.Address), + fmt.Sprintf("--fee-payer=%s", "invalid-fee-payer"), + }, + wantErr: "decoding bech32 failed", + }, + { + name: "sad: contract addr", + args: []string{"update", "sadcontract", addrs[1].String()}, + extraArgs: []string{ + fmt.Sprintf("--from=%s", s.testAcc.Address), + }, + wantErr: "invalid contract", + }, + { + name: "sad: new withdraw addr", + args: []string{"update", addrs[0].String(), "saddeployer"}, + extraArgs: []string{ + fmt.Sprintf("--from=%s", s.testAcc.Address), + }, + wantErr: "invalid withdraw address", + }, + } + + for _, tc := range testCases { + tc.Run(s) + } +} diff --git a/x/sudo/module.go b/x/sudo/module.go index 72f35fbae..1e0031ad1 100644 --- a/x/sudo/module.go +++ b/x/sudo/module.go @@ -11,18 +11,21 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" "github.com/NibiruChain/nibiru/x/sudo/cli" sudokeeper "github.com/NibiruChain/nibiru/x/sudo/keeper" + simulation "github.com/NibiruChain/nibiru/x/sudo/simulation" "github.com/NibiruChain/nibiru/x/sudo/types" ) // Ensure the interface is properly implemented at compile time var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModule{} ) // ---------------------------------------------------------------------------- @@ -151,3 +154,21 @@ func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } + +//---------------------------------------------------------------------------- +// AppModuleSimulation functions +//---------------------------------------------------------------------------- + +// GenerateGenesisState implements module.AppModuleSimulation. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// RegisterStoreDecoder implements module.AppModuleSimulation. +func (AppModule) RegisterStoreDecoder(sdk.StoreDecoderRegistry) { +} + +// WeightedOperations implements module.AppModuleSimulation. +func (AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return nil +} diff --git a/x/sudo/simulation/genesis.go b/x/sudo/simulation/genesis.go new file mode 100644 index 000000000..89a863582 --- /dev/null +++ b/x/sudo/simulation/genesis.go @@ -0,0 +1,48 @@ +package simulation + +// DONTCOVER + +import ( + "encoding/json" + "fmt" + "math/rand" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/NibiruChain/nibiru/x/sudo/types" +) + +// Simulation parameter constants +const ( + CommunityTax = "community_tax" + WithdrawEnabled = "withdraw_enabled" +) + +// GenCommunityTax randomized CommunityTax +func GenCommunityTax(r *rand.Rand) math.LegacyDec { + return sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)) +} + +// GenWithdrawEnabled returns a randomized WithdrawEnabled parameter. +func GenWithdrawEnabled(r *rand.Rand) bool { + return r.Int63n(101) <= 95 // 95% chance of withdraws being enabled +} + +// RandomizedGenState generates a random GenesisState for distribution +func RandomizedGenState(simState *module.SimulationState) { + genState := types.GenesisState{ + Sudoers: types.Sudoers{ + Root: simState.Accounts[0].Address.String(), + Contracts: []string{}, + }, + } + + bz, err := json.MarshalIndent(&genState, "", " ") + if err != nil { + panic(err) + } + fmt.Printf("Selected randomly generated x/sudo parameters:\n%s\n", bz) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&genState) +}