From bf7768006c18ca5df1eba6fb81186c5e7177c738 Mon Sep 17 00:00:00 2001 From: Alexander Peters Date: Mon, 16 Sep 2024 15:42:48 +0200 Subject: [PATCH] feat(sims): Add sims2 framework and factory methods (#21613) --- CHANGELOG.md | 1 + docs/build/building-modules/14-simulator.md | 2 +- go.mod | 2 +- scripts/build/simulations.mk | 59 +- simapp/app.go | 8 +- simapp/sim_bench_test.go | 79 +- simapp/sim_test.go | 102 +- simsx/README.md | 42 + simsx/common_test.go | 161 ++ simsx/context.go | 17 + simsx/delivery.go | 98 ++ simsx/delivery_test.go | 74 + simsx/environment.go | 458 +++++ simsx/environment_test.go | 67 + simsx/msg_factory.go | 151 ++ simsx/msg_factory_test.go | 97 ++ simsx/params.go | 52 + simsx/registry.go | 206 +++ simsx/registry_test.go | 158 ++ simsx/reporter.go | 258 +++ simsx/reporter_test.go | 219 +++ simsx/runner.go | 377 +++++ simsx/slices.go | 38 + simsx/slices_test.go | 40 + tests/go.mod | 2 +- .../bank/keeper/deterministic_test.go | 4 +- .../distribution/keeper/msg_server_test.go | 4 +- .../evidence/keeper/infraction_test.go | 2 +- tests/integration/genutil/init_test.go | 2 +- tests/integration/gov/keeper/keeper_test.go | 2 +- .../slashing/keeper/keeper_test.go | 2 +- .../integration/staking/keeper/common_test.go | 2 +- .../staking/keeper/deterministic_test.go | 2 +- .../staking/simulation/operations_test.go | 443 ----- tests/sims/authz/operations_test.go | 224 --- tests/sims/bank/operations_test.go | 217 --- tests/sims/distribution/app_config.go | 29 - tests/sims/distribution/operations_test.go | 287 ---- tests/sims/feegrant/operations_test.go | 213 --- tests/sims/gov/operations_test.go | 428 ----- tests/sims/nft/app_config.go | 27 - tests/sims/nft/operations_test.go | 141 -- tests/sims/protocolpool/app_config.go | 29 - tests/sims/protocolpool/operations_test.go | 148 -- tests/sims/slashing/app_config.go | 31 - tests/sims/slashing/operations_test.go | 228 --- testutil/sims/simulation_helpers.go | 73 +- testutil/sims/state_helpers.go | 82 +- testutils/sims/runner.go | 251 --- types/module/simulation.go | 67 +- types/simulation/account.go | 18 +- types/simulation/config.go | 9 +- types/simulation/types.go | 29 +- x/auth/module.go | 12 +- x/auth/simulation/genesis.go | 7 - x/auth/simulation/msg_factory.go | 25 + x/auth/simulation/proposals.go | 50 - x/auth/simulation/proposals_test.go | 45 - x/authz/module/depinject.go | 3 +- x/authz/module/module.go | 30 +- x/authz/simulation/genesis.go | 56 +- x/authz/simulation/msg_factory.go | 103 ++ x/authz/simulation/operations.go | 394 ----- x/bank/module.go | 19 +- x/bank/simulation/genesis.go | 7 - x/bank/simulation/msg_factory.go | 87 + x/bank/simulation/operations.go | 472 ------ x/distribution/depinject.go | 2 +- x/distribution/go.mod | 2 +- x/distribution/module.go | 30 +- x/distribution/simulation/genesis.go | 7 - x/distribution/simulation/msg_factory.go | 125 ++ x/distribution/simulation/operations.go | 247 --- x/distribution/simulation/proposals.go | 53 - x/distribution/simulation/proposals_test.go | 47 - x/epochs/module.go | 5 - x/epochs/simulation/genesis.go | 7 - x/evidence/module.go | 5 - x/evidence/simulation/genesis.go | 7 - x/feegrant/module/depinject.go | 23 +- x/feegrant/module/module.go | 36 +- x/feegrant/simulation/msg_factory.go | 58 + x/feegrant/simulation/operations.go | 197 --- x/genutil/client/cli/validate_genesis_test.go | 2 +- x/gov/go.mod | 3 +- x/gov/module.go | 42 +- x/gov/simulation/genesis.go | 7 - x/gov/simulation/msg_factory.go | 371 ++++ x/gov/simulation/operations.go | 752 -------- x/gov/simulation/proposals.go | 40 +- x/gov/simulation/proposals_test.go | 63 - x/group/keeper/keeper.go | 2 +- x/group/keeper/keeper_test.go | 2 +- x/group/keeper/msg_server.go | 3 +- x/group/keeper/msg_server_test.go | 2 +- x/group/module/module.go | 25 +- x/group/simulation/genesis.go | 60 +- x/group/simulation/msg_factory.go | 487 ++++++ x/group/simulation/operations.go | 1508 ----------------- x/group/simulation/operations_test.go | 759 --------- x/mint/go.mod | 2 +- x/mint/module.go | 12 +- x/mint/simulation/genesis.go | 7 - x/mint/simulation/msg_factory.go | 29 + x/mint/simulation/proposals_test.go | 52 - x/nft/module/module.go | 10 +- x/nft/simulation/msg_factory.go | 66 + x/nft/simulation/operations.go | 170 -- x/params/go.mod | 9 - x/params/go.sum | 27 - x/params/module.go | 5 - x/params/simulation/operations.go | 54 - x/params/simulation/operations_test.go | 65 - x/params/simulation/proposals.go | 25 - x/params/simulation/proposals_test.go | 50 - x/protocolpool/depinject.go | 15 +- x/protocolpool/go.mod | 2 +- x/protocolpool/simulation/msg_factory.go | 37 + x/protocolpool/simulation/operations.go | 94 - x/protocolpool/simulation/proposals.go | 57 - x/protocolpool/simulation/proposals_test.go | 48 - x/simulation/client/cli/flags.go | 3 + x/simulation/log.go | 2 +- x/simulation/operation.go | 12 +- x/simulation/params.go | 4 +- x/simulation/params_test.go | 3 +- x/simulation/simulate.go | 131 +- x/simulation/simulate_test.go | 57 + x/slashing/module.go | 23 +- x/slashing/simulation/genesis.go | 7 - x/slashing/simulation/msg_factory.go | 98 ++ x/slashing/simulation/operations.go | 159 -- x/staking/depinject.go | 28 +- x/staking/go.mod | 2 +- x/staking/module.go | 19 +- x/staking/simulation/genesis.go | 8 - x/staking/simulation/msg_factory.go | 384 +++++ x/staking/simulation/operations.go | 873 ---------- x/staking/simulation/proposals.go | 56 - x/staking/simulation/proposals_test.go | 49 - 140 files changed, 4897 insertions(+), 9908 deletions(-) create mode 100644 simsx/README.md create mode 100644 simsx/common_test.go create mode 100644 simsx/context.go create mode 100644 simsx/delivery.go create mode 100644 simsx/delivery_test.go create mode 100644 simsx/environment.go create mode 100644 simsx/environment_test.go create mode 100644 simsx/msg_factory.go create mode 100644 simsx/msg_factory_test.go create mode 100644 simsx/params.go create mode 100644 simsx/registry.go create mode 100644 simsx/registry_test.go create mode 100644 simsx/reporter.go create mode 100644 simsx/reporter_test.go create mode 100644 simsx/runner.go create mode 100644 simsx/slices.go create mode 100644 simsx/slices_test.go delete mode 100644 tests/integration/staking/simulation/operations_test.go delete mode 100644 tests/sims/authz/operations_test.go delete mode 100644 tests/sims/bank/operations_test.go delete mode 100644 tests/sims/distribution/app_config.go delete mode 100644 tests/sims/distribution/operations_test.go delete mode 100644 tests/sims/feegrant/operations_test.go delete mode 100644 tests/sims/gov/operations_test.go delete mode 100644 tests/sims/nft/app_config.go delete mode 100644 tests/sims/nft/operations_test.go delete mode 100644 tests/sims/protocolpool/app_config.go delete mode 100644 tests/sims/protocolpool/operations_test.go delete mode 100644 tests/sims/slashing/app_config.go delete mode 100644 tests/sims/slashing/operations_test.go delete mode 100644 testutils/sims/runner.go create mode 100644 x/auth/simulation/msg_factory.go delete mode 100644 x/auth/simulation/proposals.go delete mode 100644 x/auth/simulation/proposals_test.go create mode 100644 x/authz/simulation/msg_factory.go delete mode 100644 x/authz/simulation/operations.go create mode 100644 x/bank/simulation/msg_factory.go delete mode 100644 x/bank/simulation/operations.go create mode 100644 x/distribution/simulation/msg_factory.go delete mode 100644 x/distribution/simulation/operations.go delete mode 100644 x/distribution/simulation/proposals.go delete mode 100644 x/distribution/simulation/proposals_test.go create mode 100644 x/feegrant/simulation/msg_factory.go delete mode 100644 x/feegrant/simulation/operations.go create mode 100644 x/gov/simulation/msg_factory.go delete mode 100644 x/gov/simulation/operations.go delete mode 100644 x/gov/simulation/proposals_test.go create mode 100644 x/group/simulation/msg_factory.go delete mode 100644 x/group/simulation/operations.go delete mode 100644 x/group/simulation/operations_test.go create mode 100644 x/mint/simulation/msg_factory.go delete mode 100644 x/mint/simulation/proposals_test.go create mode 100644 x/nft/simulation/msg_factory.go delete mode 100644 x/nft/simulation/operations.go delete mode 100644 x/params/simulation/operations.go delete mode 100644 x/params/simulation/operations_test.go delete mode 100644 x/params/simulation/proposals.go delete mode 100644 x/params/simulation/proposals_test.go create mode 100644 x/protocolpool/simulation/msg_factory.go delete mode 100644 x/protocolpool/simulation/operations.go delete mode 100644 x/protocolpool/simulation/proposals.go delete mode 100644 x/protocolpool/simulation/proposals_test.go create mode 100644 x/simulation/simulate_test.go create mode 100644 x/slashing/simulation/msg_factory.go delete mode 100644 x/slashing/simulation/operations.go create mode 100644 x/staking/simulation/msg_factory.go delete mode 100644 x/staking/simulation/operations.go delete mode 100644 x/staking/simulation/proposals.go delete mode 100644 x/staking/simulation/proposals_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 021b7800dbd2..6641b37dfddc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i * (baseapp) [#21256](https://github.com/cosmos/cosmos-sdk/pull/21256) Halt height will not commit the block indicated, meaning that if halt-height is set to 10, only blocks until 9 (included) will be committed. This is to go back to the original behavior before a change was introduced in v0.50.0. ### API Breaking Changes +* (sims)[#21613](https://github.com/cosmos/cosmos-sdk/pull/21613) Add sims2 framework and factory methods for simpler message factories in modules ### Deprecated diff --git a/docs/build/building-modules/14-simulator.md b/docs/build/building-modules/14-simulator.md index 0d8b4c5a861a..ca6f5ae2e67f 100644 --- a/docs/build/building-modules/14-simulator.md +++ b/docs/build/building-modules/14-simulator.md @@ -117,7 +117,7 @@ func NewCustomApp(...) { gov.NewAppModule(app.govKeeper, app.accountKeeper, app.supplyKeeper), mint.NewAppModule(app.mintKeeper), distr.NewAppModule(app.distrKeeper, app.accountKeeper, app.supplyKeeper, app.stakingKeeper), - staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper), + staking.NewAppModule(cdc, app.stakingKeeper), slashing.NewAppModule(app.slashingKeeper, app.accountKeeper, app.stakingKeeper), ) diff --git a/go.mod b/go.mod index ab8bd9738c9e..ba2ea2a21919 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,7 @@ require ( github.com/tendermint/go-amino v0.16.0 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b golang.org/x/crypto v0.27.0 + golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc golang.org/x/sync v0.8.0 google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 google.golang.org/grpc v1.66.2 @@ -160,7 +161,6 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sys v0.25.0 // indirect diff --git a/scripts/build/simulations.mk b/scripts/build/simulations.mk index 4020d468a920..33606f13dedb 100644 --- a/scripts/build/simulations.mk +++ b/scripts/build/simulations.mk @@ -2,7 +2,7 @@ #? test-sim-nondeterminism: Run non-determinism test for simapp test-sim-nondeterminism: @echo "Running non-determinism test..." - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout=30m -tags='sims' -run TestAppStateDeterminism \ + @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout=30m -tags='sims' -run TestAppStateDeterminism \ -NumBlocks=100 -BlockSize=200 -Period=0 # Requires an exported plugin. See store/streaming/README.md for documentation. @@ -16,45 +16,49 @@ test-sim-nondeterminism: # make test-sim-nondeterminism-streaming test-sim-nondeterminism-streaming: @echo "Running non-determinism-streaming test..." - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout=30m -tags='sims' -run TestAppStateDeterminism \ + @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout=30m -tags='sims' -run TestAppStateDeterminism \ -NumBlocks=100 -BlockSize=200 -Period=0 -EnableStreaming=true test-sim-custom-genesis-fast: @echo "Running custom genesis simulation..." @echo "By default, ${HOME}/.simapp/config/genesis.json will be used." - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout=30m -tags='sims' -run TestFullAppSimulation -Genesis=${HOME}/.simapp/config/genesis.json \ + @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout=30m -tags='sims' -run TestFullAppSimulation -Genesis=${HOME}/.simapp/config/genesis.json \ -NumBlocks=100 -BlockSize=200 -Seed=99 -Period=5 -SigverifyTx=false test-sim-import-export: @echo "Running application import/export simulation. This may take several minutes..." - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout 20m -tags='sims' -run TestAppImportExport \ + @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout 20m -tags='sims' -run TestAppImportExport \ -NumBlocks=50 -Period=5 test-sim-after-import: @echo "Running application simulation-after-import. This may take several minutes..." - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout 30m -tags='sims' -run TestAppSimulationAfterImport \ + @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestAppSimulationAfterImport \ -NumBlocks=50 -Period=5 test-sim-custom-genesis-multi-seed: @echo "Running multi-seed custom genesis simulation..." @echo "By default, ${HOME}/.simapp/config/genesis.json will be used." - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout 30m -tags='sims' -run TestFullAppSimulation -Genesis=${HOME}/.simapp/config/genesis.json \ + @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestFullAppSimulation -Genesis=${HOME}/.simapp/config/genesis.json \ -NumBlocks=400 -Period=5 test-sim-multi-seed-long: @echo "Running long multi-seed application simulation. This may take awhile!" - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout=2h -tags='sims' -run TestFullAppSimulation \ + @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout=2h -tags='sims' -run TestFullAppSimulation \ -NumBlocks=150 -Period=50 test-sim-multi-seed-short: @echo "Running short multi-seed application simulation. This may take awhile!" - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout 30m -tags='sims' -run TestFullAppSimulation \ - -NumBlocks=50 -Period=10 + @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestFullAppSimulation \ + -NumBlocks=50 -Period=10 -FauxMerkle=true + +test-v2-sim-wip: + @echo "Running v2 simapp. This may take awhile!" + @cd ${CURRENT_DIR}/simapp/v2 && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestSimsAppV2 test-sim-benchmark-invariants: @echo "Running simulation invariant benchmarks..." - cd ${CURRENT_DIR}/simapp && go test -mod=readonly -benchmem -bench=BenchmarkInvariants -tags='sims' -run=^$ \ + cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -benchmem -bench=BenchmarkInvariants -tags='sims' -run=^$ \ -Enabled=true -NumBlocks=1000 -BlockSize=200 \ -Period=1 -Commit=true -Seed=57 -v -timeout 24h @@ -77,50 +81,23 @@ SIM_COMMIT ?= true test-sim-fuzz: @echo "Running application fuzz for numBlocks=2, blockSize=20. This may take awhile!" #ld flags are a quick fix to make it work on current osx - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -json -tags='sims' -ldflags="-extldflags=-Wl,-ld_classic" -timeout=60m -fuzztime=60m -run=^$$ -fuzz=FuzzFullAppSimulation -GenesisTime=1714720615 -NumBlocks=2 -BlockSize=20 + @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -json -tags='sims' -ldflags="-extldflags=-Wl,-ld_classic" -timeout=60m -fuzztime=60m -run=^$$ -fuzz=FuzzFullAppSimulation -GenesisTime=1714720615 -NumBlocks=2 -BlockSize=20 #? test-sim-benchmark: Run benchmark test for simapp test-sim-benchmark: @echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -tags='sims' -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \ + @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -tags='sims' -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \ -Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -# Requires an exported plugin. See store/streaming/README.md for documentation. -# -# example: -# export COSMOS_SDK_ABCI_V1= -# make test-sim-benchmark-streaming -# -# Using the built-in examples: -# export COSMOS_SDK_ABCI_V1=/store/streaming/abci/examples/file/file -# make test-sim-benchmark-streaming -test-sim-benchmark-streaming: - @echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -tags='sims' -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \ - -Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -EnableStreaming=true test-sim-profile: @echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -tags='sims' -benchmem -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \ + @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -tags='sims' -benchmem -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \ -Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out -# Requires an exported plugin. See store/streaming/README.md for documentation. -# -# example: -# export COSMOS_SDK_ABCI_V1= -# make test-sim-profile-streaming -# -# Using the built-in examples: -# export COSMOS_SDK_ABCI_V1=/store/streaming/abci/examples/file/file -# make test-sim-profile-streaming -test-sim-profile-streaming: - @echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" - @cd ${CURRENT_DIR}/simapp && go test -mod=readonly -tags='sims' -benchmem -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \ - -Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out -EnableStreaming=true - .PHONY: test-sim-profile test-sim-benchmark test-sim-fuzz #? benchmark: Run benchmark tests benchmark: - @go test -mod=readonly -bench=. $(PACKAGES_NOSIMULATION) + @go test -failfast -mod=readonly -bench=. $(PACKAGES_NOSIMULATION) .PHONY: benchmark diff --git a/simapp/app.go b/simapp/app.go index 00f27cacb87e..d7309c890611 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -463,15 +463,15 @@ func NewSimApp( auth.NewAppModule(appCodec, app.AuthKeeper, app.AccountsKeeper, authsims.RandomGenesisAccounts, nil), vesting.NewAppModule(app.AuthKeeper, app.BankKeeper), bank.NewAppModule(appCodec, app.BankKeeper, app.AuthKeeper), - feegrantmodule.NewAppModule(appCodec, app.AuthKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), + feegrantmodule.NewAppModule(appCodec, app.FeeGrantKeeper, app.interfaceRegistry), gov.NewAppModule(appCodec, &app.GovKeeper, app.AuthKeeper, app.BankKeeper, app.PoolKeeper), mint.NewAppModule(appCodec, app.MintKeeper, app.AuthKeeper, nil), slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AuthKeeper, app.BankKeeper, app.StakingKeeper, app.interfaceRegistry, cometService), - distr.NewAppModule(appCodec, app.DistrKeeper, app.AuthKeeper, app.BankKeeper, app.StakingKeeper), - staking.NewAppModule(appCodec, app.StakingKeeper, app.AuthKeeper, app.BankKeeper), + distr.NewAppModule(appCodec, app.DistrKeeper, app.StakingKeeper), + staking.NewAppModule(appCodec, app.StakingKeeper), upgrade.NewAppModule(app.UpgradeKeeper), evidence.NewAppModule(appCodec, app.EvidenceKeeper, cometService), - authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AuthKeeper, app.BankKeeper, app.interfaceRegistry), + authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.interfaceRegistry), groupmodule.NewAppModule(appCodec, app.GroupKeeper, app.AuthKeeper, app.BankKeeper, app.interfaceRegistry), nftmodule.NewAppModule(appCodec, app.NFTKeeper, app.AuthKeeper, app.BankKeeper, app.interfaceRegistry), consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), diff --git a/simapp/sim_bench_test.go b/simapp/sim_bench_test.go index ba5f02652ef5..cf1361aaf299 100644 --- a/simapp/sim_bench_test.go +++ b/simapp/sim_bench_test.go @@ -3,92 +3,19 @@ package simapp import ( - "os" + "github.com/cosmos/cosmos-sdk/simsx" "testing" - flag "github.com/spf13/pflag" - "github.com/spf13/viper" - "github.com/stretchr/testify/require" - - "cosmossdk.io/log" - - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/server" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - "github.com/cosmos/cosmos-sdk/testutils/sims" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" simcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" ) -var FlagEnableBenchStreamingValue bool - -// Get flags every time the simulator is run -func init() { - flag.BoolVar(&FlagEnableBenchStreamingValue, "EnableStreaming", false, "Enable streaming service") -} - // Profile with: // /usr/local/go/bin/go test -benchmem -run=^$ cosmossdk.io/simapp -bench ^BenchmarkFullAppSimulation$ -Commit=true -cpuprofile cpu.out func BenchmarkFullAppSimulation(b *testing.B) { b.ReportAllocs() config := simcli.NewConfigFromFlags() - config.ChainID = sims.SimAppChainID - - db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "goleveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue) - if err != nil { - b.Fatalf("simulation setup failed: %s", err.Error()) - } - - if skip { - b.Skip("skipping benchmark application simulation") - } - - defer func() { - require.NoError(b, db.Close()) - require.NoError(b, os.RemoveAll(dir)) - }() - - appOptions := viper.New() - appOptions.SetDefault(flags.FlagHome, DefaultNodeHome) - appOptions.SetDefault(server.FlagInvCheckPeriod, simcli.FlagPeriodValue) - - app := NewSimApp(logger, db, nil, true, appOptions, interBlockCacheOpt(), baseapp.SetChainID(sims.SimAppChainID)) - - blockedAddrs, err := BlockedAddresses(app.InterfaceRegistry().SigningContext().AddressCodec()) - require.NoError(b, err) - - // run randomized simulation - simParams, simErr := simulation.SimulateFromSeedX( - b, - log.NewNopLogger(), - os.Stdout, - app.BaseApp, - simtestutil.AppStateFn(app.AppCodec(), app.AuthKeeper.AddressCodec(), app.StakingKeeper.ValidatorAddressCodec(), app.SimulationManager(), app.DefaultGenesis()), - simtypes.RandomAccounts, - simtestutil.SimulationOperations(app, app.AppCodec(), config, app.txConfig), - blockedAddrs, - config, - app.AppCodec(), - app.txConfig.SigningContext().AddressCodec(), - &simulation.DummyLogWriter{}, - ) - - // export state and simParams before the simulation error is checked - if err = simtestutil.CheckExportSimulation(app, config, simParams); err != nil { - b.Fatal(err) - } - - if simErr != nil { - b.Fatal(simErr) - } + config.ChainID = simsx.SimAppChainID - if config.Commit { - db, ok := db.(simtestutil.DBStatsInterface) - if ok { - simtestutil.PrintStats(db) - } - } + simsx.RunWithSeed(b, config, NewSimApp, setupStateFactory, 1, nil) } diff --git a/simapp/sim_test.go b/simapp/sim_test.go index 9cb61c051adf..b88a97601de5 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -11,6 +11,12 @@ import ( "strings" "sync" "testing" + "time" + + abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" + cmtproto "github.com/cometbft/cometbft/api/cometbft/types/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" corestore "cosmossdk.io/core/store" "cosmossdk.io/log" @@ -20,18 +26,15 @@ import ( "cosmossdk.io/x/feegrant" slashingtypes "cosmossdk.io/x/slashing/types" stakingtypes "cosmossdk.io/x/staking/types" - abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" - cmtproto "github.com/cometbft/cometbft/api/cometbft/types/v1" + "github.com/cosmos/cosmos-sdk/baseapp" servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/simsx" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - "github.com/cosmos/cosmos-sdk/testutils/sims" sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" simcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) // SimAppChainID hardcoded chainID for simulation @@ -51,32 +54,34 @@ func interBlockCacheOpt() func(*baseapp.BaseApp) { } func TestFullAppSimulation(t *testing.T) { - sims.Run(t, NewSimApp, setupStateFactory) + simsx.Run(t, NewSimApp, setupStateFactory) } -func setupStateFactory(app *SimApp) sims.SimStateFactory { +func setupStateFactory(app *SimApp) simsx.SimStateFactory { blockedAddre, _ := BlockedAddresses(app.interfaceRegistry.SigningContext().AddressCodec()) - return sims.SimStateFactory{ - Codec: app.AppCodec(), - AppStateFn: simtestutil.AppStateFn(app.AppCodec(), app.AuthKeeper.AddressCodec(), app.StakingKeeper.ValidatorAddressCodec(), app.SimulationManager(), app.DefaultGenesis()), - BlockedAddr: blockedAddre, + return simsx.SimStateFactory{ + Codec: app.AppCodec(), + AppStateFn: simtestutil.AppStateFn(app.AppCodec(), app.AuthKeeper.AddressCodec(), app.StakingKeeper.ValidatorAddressCodec(), app.SimulationManager().Modules, app.DefaultGenesis()), + BlockedAddr: blockedAddre, + AccountSource: app.AuthKeeper, + BalanceSource: app.BankKeeper, } } var ( - exportAllModules = []string{} - exportWithValidatorSet = []string{} + exportAllModules []string + exportWithValidatorSet []string ) func TestAppImportExport(t *testing.T) { - sims.Run(t, NewSimApp, setupStateFactory, func(t *testing.T, ti sims.TestInstance[*SimApp]) { + simsx.Run(t, NewSimApp, setupStateFactory, func(t testing.TB, ti simsx.TestInstance[*SimApp]) { app := ti.App t.Log("exporting genesis...\n") exported, err := app.ExportAppStateAndValidators(false, exportWithValidatorSet, exportAllModules) require.NoError(t, err) t.Log("importing genesis...\n") - newTestInstance := sims.NewSimulationAppInstance(t, ti.Cfg, NewSimApp) + newTestInstance := simsx.NewSimulationAppInstance(t, ti.Cfg, NewSimApp) newApp := newTestInstance.App var genesisState GenesisState require.NoError(t, json.Unmarshal(exported.AppState, &genesisState)) @@ -111,40 +116,36 @@ func TestAppImportExport(t *testing.T) { // set up a new node instance, Init chain from exported genesis // run new instance for n blocks func TestAppSimulationAfterImport(t *testing.T) { - sims.Run(t, NewSimApp, setupStateFactory, func(t *testing.T, ti sims.TestInstance[*SimApp]) { + simsx.Run(t, NewSimApp, setupStateFactory, func(t testing.TB, ti simsx.TestInstance[*SimApp]) { app := ti.App t.Log("exporting genesis...\n") exported, err := app.ExportAppStateAndValidators(false, exportWithValidatorSet, exportAllModules) require.NoError(t, err) t.Log("importing genesis...\n") - newTestInstance := sims.NewSimulationAppInstance(t, ti.Cfg, NewSimApp) - newApp := newTestInstance.App - _, err = newApp.InitChain(&abci.InitChainRequest{ - AppStateBytes: exported.AppState, - ChainId: sims.SimAppChainID, - }) - if IsEmptyValidatorSetErr(err) { - t.Skip("Skipping simulation as all validators have been unbonded") - return + importGenesisStateFactory := func(app *SimApp) simsx.SimStateFactory { + return simsx.SimStateFactory{ + Codec: app.AppCodec(), + AppStateFn: func(r *rand.Rand, accs []simtypes.Account, config simtypes.Config) (json.RawMessage, []simtypes.Account, string, time.Time) { + _, err = app.InitChain(&abci.InitChainRequest{ + AppStateBytes: exported.AppState, + ChainId: simsx.SimAppChainID, + }) + if IsEmptyValidatorSetErr(err) { + t.Skip("Skipping simulation as all validators have been unbonded") + return nil, nil, "", time.Time{} + } + acc, err := simtestutil.AccountsFromAppState(app.AppCodec(), exported.AppState) + require.NoError(t, err) + genesisTimestamp := time.Unix(config.GenesisTime, 0) + return exported.AppState, acc, config.ChainID, genesisTimestamp + }, + BlockedAddr: must(BlockedAddresses(app.AuthKeeper.AddressCodec())), + AccountSource: app.AuthKeeper, + BalanceSource: app.BankKeeper, + } } - require.NoError(t, err) - newStateFactory := setupStateFactory(newApp) - _, err = simulation.SimulateFromSeedX( - t, - newTestInstance.AppLogger, - sims.WriteToDebugLog(newTestInstance.AppLogger), - newApp.BaseApp, - newStateFactory.AppStateFn, - simtypes.RandomAccounts, - simtestutil.SimulationOperations(newApp, newApp.AppCodec(), newTestInstance.Cfg, newApp.TxConfig()), - newStateFactory.BlockedAddr, - newTestInstance.Cfg, - newStateFactory.Codec, - newApp.TxConfig().SigningContext().AddressCodec(), - ti.ExecLogWriter, - ) - require.NoError(t, err) + simsx.RunWithSeed(t, ti.Cfg, NewSimApp, importGenesisStateFactory, ti.Cfg.Seed, ti.Cfg.FuzzSeed) }) } @@ -178,7 +179,7 @@ func TestAppStateDeterminism(t *testing.T) { "streaming.abci.stop-node-on-err": true, } others := appOpts - appOpts = sims.AppOptionsFn(func(k string) any { + appOpts = simsx.AppOptionsFn(func(k string) any { if v, ok := m[k]; ok { return v } @@ -190,7 +191,7 @@ func TestAppStateDeterminism(t *testing.T) { var mx sync.Mutex appHashResults := make(map[int64][][]byte) appSimLogger := make(map[int64][]simulation.LogWriter) - captureAndCheckHash := func(t *testing.T, ti sims.TestInstance[*SimApp]) { + captureAndCheckHash := func(t testing.TB, ti simsx.TestInstance[*SimApp]) { seed, appHash := ti.Cfg.Seed, ti.App.LastCommitID().Hash mx.Lock() otherHashes, execWriters := appHashResults[seed], appSimLogger[seed] @@ -216,7 +217,7 @@ func TestAppStateDeterminism(t *testing.T) { } } // run simulations - sims.RunWithSeeds(t, interBlockCachingAppFactory, setupStateFactory, seeds, []byte{}, captureAndCheckHash) + simsx.RunWithSeeds(t, interBlockCachingAppFactory, setupStateFactory, seeds, []byte{}, captureAndCheckHash) } type ComparableStoreApp interface { @@ -226,7 +227,7 @@ type ComparableStoreApp interface { GetStoreKeys() []storetypes.StoreKey } -func AssertEqualStores(t *testing.T, app ComparableStoreApp, newApp ComparableStoreApp, storeDecoders simtypes.StoreDecoderRegistry, skipPrefixes map[string][][]byte) { +func AssertEqualStores(t testing.TB, app, newApp ComparableStoreApp, storeDecoders simtypes.StoreDecoderRegistry, skipPrefixes map[string][][]byte) { ctxA := app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()}) ctxB := newApp.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()}) @@ -264,7 +265,7 @@ func FuzzFullAppSimulation(f *testing.F) { t.Skip() return } - sims.RunWithSeeds( + simsx.RunWithSeeds( t, NewSimApp, setupStateFactory, @@ -273,3 +274,10 @@ func FuzzFullAppSimulation(f *testing.F) { ) }) } + +func must[T any](r T, err error) T { + if err != nil { + panic(err) + } + return r +} diff --git a/simsx/README.md b/simsx/README.md new file mode 100644 index 000000000000..fe1cc2058113 --- /dev/null +++ b/simsx/README.md @@ -0,0 +1,42 @@ +# Simsx + +This package introduces some new helper types to simplify message construction for simulations (sims). The focus is on better dev UX for new message factories. +Technically, they are adapters that build upon the existing sims framework. + +#### * [Message factory](https://github.com/cosmos/cosmos-sdk/blob/main/simsx/msg_factory.go) + +Simple functions as factories for dedicated sdk.Msgs. They have access to the context, reporter and test data environment. For example: +```go +func MsgSendFactory() simsx.SimMsgFactoryFn[*types.MsgSend] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgSend) { + from := testData.AnyAccount(reporter, simsx.WithSpendableBalance()) + to := testData.AnyAccount(reporter, simsx.ExcludeAccounts(from)) + coins := from.LiquidBalance().RandSubsetCoins(reporter, simsx.WithSendEnabledCoins()) + return []simsx.SimAccount{from}, types.NewMsgSend(from.AddressBech32, to.AddressBech32, coins) + } +} +``` + +#### * [Sims registry](https://github.com/cosmos/cosmos-sdk/blob/main/simsx/registry.go) + +A new helper to register message factories with a default weight value. They can be overwritten by a parameters file as before. The registry is passed to the AppModule type. For example: +```go +func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_send", 100), simulation.MsgSendFactory()) + reg.Add(weights.Get("msg_multisend", 10), simulation.MsgMultiSendFactory()) +} +``` + +#### * [Reporter](https://github.com/cosmos/cosmos-sdk/blob/main/simsx/reporter.go) +The reporter is a flow control structure that can be used in message factories to skip execution at any point. The idea is similar to the testing.T Skip in Go stdlib. Internally, it converts skip, success and failure events to legacy sim messages. +The reporter also provides some capability to print an execution summary. +It is also used to interact with the test data environment to not have errors checked all the time. +Message factories may want to abort early via +```go +if reporter.IsSkipped() { + return nil, nil +} +``` + +#### * [Test data environment](https://github.com/cosmos/cosmos-sdk/blob/main/simsx/environment.go) +The test data environment provides simple access to accounts and other test data used in most message factories. It also encapsulates some app internals like bank keeper or address codec. diff --git a/simsx/common_test.go b/simsx/common_test.go new file mode 100644 index 000000000000..43fcd7d41860 --- /dev/null +++ b/simsx/common_test.go @@ -0,0 +1,161 @@ +package simsx + +import ( + "context" + "math/rand" + + "github.com/cosmos/gogoproto/proto" + + coretransaction "cosmossdk.io/core/transaction" + "cosmossdk.io/x/tx/signing" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/address" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/auth/tx" +) + +// SimAccountFixture testing only +func SimAccountFixture(mutators ...func(account *SimAccount)) SimAccount { + r := rand.New(rand.NewSource(1)) + acc := SimAccount{ + Account: simtypes.RandomAccounts(r, 1)[0], + } + acc.liquidBalance = NewSimsAccountBalance(&acc, r, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1_000_000_000))) + for _, mutator := range mutators { + mutator(&acc) + } + return acc +} + +// MemoryAccountSource testing only +func MemoryAccountSource(srcs ...SimAccount) AccountSourceFn { + accs := make(map[string]FakeAccountI, len(srcs)) + for _, src := range srcs { + accs[src.AddressBech32] = FakeAccountI{SimAccount: src, id: 1, seq: 2} + } + return func(ctx context.Context, addr sdk.AccAddress) sdk.AccountI { + return accs[addr.String()] + } +} + +// testing only +func txConfig() client.TxConfig { + ir := must(codectypes.NewInterfaceRegistryWithOptions(codectypes.InterfaceRegistryOptions{ + ProtoFiles: proto.HybridResolver, + SigningOptions: signing.Options{ + AddressCodec: address.NewBech32Codec("cosmos"), + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + }, + })) + std.RegisterInterfaces(ir) + ir.RegisterImplementations((*coretransaction.Msg)(nil), &testdata.TestMsg{}) + protoCodec := codec.NewProtoCodec(ir) + signingCtx := protoCodec.InterfaceRegistry().SigningContext() + return tx.NewTxConfig(protoCodec, signingCtx.AddressCodec(), signingCtx.ValidatorAddressCodec(), tx.DefaultSignModes) +} + +var _ AppEntrypoint = SimDeliverFn(nil) + +type ( + AppEntrypointFn = SimDeliverFn + SimDeliverFn func(_txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) +) + +func (m SimDeliverFn) SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { + return m(txEncoder, tx) +} + +var _ AccountSource = AccountSourceFn(nil) + +type AccountSourceFn func(ctx context.Context, addr sdk.AccAddress) sdk.AccountI + +func (a AccountSourceFn) GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI { + return a(ctx, addr) +} + +var _ sdk.AccountI = &FakeAccountI{} + +type FakeAccountI struct { + SimAccount + id, seq uint64 +} + +func (m FakeAccountI) GetAddress() sdk.AccAddress { + return m.Address +} + +func (m FakeAccountI) GetPubKey() cryptotypes.PubKey { + return m.PubKey +} + +func (m FakeAccountI) GetAccountNumber() uint64 { + return m.id +} + +func (m FakeAccountI) GetSequence() uint64 { + return m.seq +} + +func (m FakeAccountI) Reset() { + panic("implement me") +} + +func (m FakeAccountI) String() string { + panic("implement me") +} + +func (m FakeAccountI) ProtoMessage() { + panic("implement me") +} + +func (m FakeAccountI) SetAddress(address sdk.AccAddress) error { + panic("implement me") +} + +func (m FakeAccountI) SetPubKey(key cryptotypes.PubKey) error { + panic("implement me") +} + +func (m FakeAccountI) SetAccountNumber(u uint64) error { + panic("implement me") +} + +func (m FakeAccountI) SetSequence(u uint64) error { + panic("implement me") +} + +var _ AccountSourceX = &MockAccountSourceX{} + +// MockAccountSourceX mock impl for testing only +type MockAccountSourceX struct { + GetAccountFn func(ctx context.Context, addr sdk.AccAddress) sdk.AccountI + GetModuleAddressFn func(moduleName string) sdk.AccAddress +} + +func (m MockAccountSourceX) GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI { + if m.GetAccountFn == nil { + panic("not expected to be called") + } + return m.GetAccountFn(ctx, addr) +} + +func (m MockAccountSourceX) GetModuleAddress(moduleName string) sdk.AccAddress { + if m.GetModuleAddressFn == nil { + panic("not expected to be called") + } + return m.GetModuleAddressFn(moduleName) +} + +func must[T any](r T, err error) T { + if err != nil { + panic(err) + } + return r +} diff --git a/simsx/context.go b/simsx/context.go new file mode 100644 index 000000000000..13bdc10efad3 --- /dev/null +++ b/simsx/context.go @@ -0,0 +1,17 @@ +package simsx + +import ( + "context" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// BlockTime read header block time from sdk context or sims context key if not present +func BlockTime(ctx context.Context) time.Time { + sdkCtx, ok := sdk.TryUnwrapSDKContext(ctx) + if ok { + return sdkCtx.BlockTime() + } + return ctx.Value("sims.header.time").(time.Time) +} diff --git a/simsx/delivery.go b/simsx/delivery.go new file mode 100644 index 000000000000..f67c9cc79b88 --- /dev/null +++ b/simsx/delivery.go @@ -0,0 +1,98 @@ +package simsx + +import ( + "context" + "errors" + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/client" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +type ( + // AppEntrypoint is an alias to the simtype interface + AppEntrypoint = simtypes.AppEntrypoint + + AccountSource interface { + GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI + } + // SimDeliveryResultHandler processes the delivery response error. Some sims are supposed to fail and expect an error. + // An unhandled error returned indicates a failure + SimDeliveryResultHandler func(error) error +) + +// DeliverSimsMsg delivers a simulation message by creating and signing a mock transaction, +// then delivering it to the application through the specified entrypoint. It returns a legacy +// operation message representing the result of the delivery. +// +// The function takes the following parameters: +// - reporter: SimulationReporter - Interface for reporting the result of the delivery +// - r: *rand.Rand - Random number generator used for creating the mock transaction +// - app: AppEntrypoint - Entry point for delivering the simulation transaction to the application +// - txGen: client.TxConfig - Configuration for generating transactions +// - ak: AccountSource - Source for retrieving accounts +// - msg: sdk.Msg - The simulation message to be delivered +// - ctx: sdk.Context - The simulation context +// - chainID: string - The chain ID +// - senders: ...SimAccount - Accounts from which to send the simulation message +// +// The function returns a simtypes.OperationMsg, which is a legacy representation of the result +// of the delivery. +func DeliverSimsMsg( + ctx context.Context, + reporter SimulationReporter, + app AppEntrypoint, + r *rand.Rand, + txGen client.TxConfig, + ak AccountSource, + chainID string, + msg sdk.Msg, + deliveryResultHandler SimDeliveryResultHandler, + senders ...SimAccount, +) simtypes.OperationMsg { + if reporter.IsSkipped() { + return reporter.ToLegacyOperationMsg() + } + if len(senders) == 0 { + reporter.Fail(errors.New("no senders"), "encoding TX") + return reporter.ToLegacyOperationMsg() + } + accountNumbers := make([]uint64, len(senders)) + sequenceNumbers := make([]uint64, len(senders)) + for i := 0; i < len(senders); i++ { + acc := ak.GetAccount(ctx, senders[i].Address) + accountNumbers[i] = acc.GetAccountNumber() + sequenceNumbers[i] = acc.GetSequence() + } + fees := senders[0].LiquidBalance().RandFees() + tx, err := sims.GenSignedMockTx( + r, + txGen, + []sdk.Msg{msg}, + fees, + sims.DefaultGenTxGas, + chainID, + accountNumbers, + sequenceNumbers, + Collect(senders, func(a SimAccount) cryptotypes.PrivKey { return a.PrivKey })..., + ) + if err != nil { + reporter.Fail(err, "encoding TX") + return reporter.ToLegacyOperationMsg() + } + _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) + if err2 := deliveryResultHandler(err); err2 != nil { + var comment string + for _, msg := range tx.GetMsgs() { + comment += fmt.Sprintf("%#v", msg) + } + reporter.Fail(err2, fmt.Sprintf("delivering tx with msgs: %s", comment)) + return reporter.ToLegacyOperationMsg() + } + reporter.Success(msg) + return reporter.ToLegacyOperationMsg() +} diff --git a/simsx/delivery_test.go b/simsx/delivery_test.go new file mode 100644 index 000000000000..627841fcb9af --- /dev/null +++ b/simsx/delivery_test.go @@ -0,0 +1,74 @@ +package simsx + +import ( + "errors" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +func TestDeliverSimsMsg(t *testing.T) { + var ( + sender = SimAccountFixture() + ak = MemoryAccountSource(sender) + myMsg = testdata.NewTestMsg(sender.Address) + txConfig = txConfig() + r = rand.New(rand.NewSource(1)) + ctx sdk.Context + ) + noopResultHandler := func(err error) error { return err } + specs := map[string]struct { + app AppEntrypoint + reporter func() SimulationReporter + deliveryResultHandler SimDeliveryResultHandler + errDeliveryResultHandler error + expOps simtypes.OperationMsg + }{ + "error when reporter skipped": { + app: SimDeliverFn(func(_txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { + return sdk.GasInfo{GasWanted: 100, GasUsed: 20}, &sdk.Result{}, nil + }), + reporter: func() SimulationReporter { + r := NewBasicSimulationReporter() + r.Skip("testing") + return r + }, + expOps: simtypes.NoOpMsg("", "", "testing"), + }, + "successful delivery": { + app: SimDeliverFn(func(_txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { + return sdk.GasInfo{GasWanted: 100, GasUsed: 20}, &sdk.Result{}, nil + }), + reporter: func() SimulationReporter { return NewBasicSimulationReporter() }, + deliveryResultHandler: noopResultHandler, + expOps: simtypes.NewOperationMsgBasic("", "", "", true), + }, + "error delivery": { + app: SimDeliverFn(func(_txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { + return sdk.GasInfo{GasWanted: 100, GasUsed: 20}, &sdk.Result{}, errors.New("my error") + }), + reporter: func() SimulationReporter { return NewBasicSimulationReporter() }, + deliveryResultHandler: noopResultHandler, + expOps: simtypes.NewOperationMsgBasic("", "", "delivering tx with msgs: &testdata.TestMsg{Signers:[]string{\"cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3\"}, DecField:0.000000000000000000}", false), + }, + "error delivery handled": { + app: SimDeliverFn(func(_txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { + return sdk.GasInfo{GasWanted: 100, GasUsed: 20}, &sdk.Result{}, errors.New("my error") + }), + reporter: func() SimulationReporter { return NewBasicSimulationReporter() }, + deliveryResultHandler: func(err error) error { return nil }, + expOps: simtypes.NewOperationMsgBasic("", "", "", true), + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got := DeliverSimsMsg(ctx, spec.reporter(), spec.app, r, txConfig, ak, "testing", myMsg, spec.deliveryResultHandler, sender) + assert.Equal(t, spec.expOps, got) + }) + } +} diff --git a/simsx/environment.go b/simsx/environment.go new file mode 100644 index 000000000000..b602790ffe4e --- /dev/null +++ b/simsx/environment.go @@ -0,0 +1,458 @@ +package simsx + +import ( + "context" + "errors" + "math/rand" + "slices" + "time" + + "cosmossdk.io/core/address" + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +// helper type for simple bank access +type contextAwareBalanceSource struct { + ctx context.Context + bank BalanceSource +} + +func (s contextAwareBalanceSource) SpendableCoins(accAddress sdk.AccAddress) sdk.Coins { + return s.bank.SpendableCoins(s.ctx, accAddress) +} + +func (s contextAwareBalanceSource) IsSendEnabledDenom(denom string) bool { + return s.bank.IsSendEnabledDenom(s.ctx, denom) +} + +// SimAccount is an extended simtypes.Account +type SimAccount struct { + simtypes.Account + r *rand.Rand + liquidBalance *SimsAccountBalance + bank contextAwareBalanceSource +} + +// LiquidBalance spendable balance. This excludes not spendable amounts like staked or vested amounts. +func (a *SimAccount) LiquidBalance() *SimsAccountBalance { + if a.liquidBalance == nil { + a.liquidBalance = NewSimsAccountBalance(a, a.r, a.bank.SpendableCoins(a.Address)) + } + return a.liquidBalance +} + +// SimsAccountBalance is a helper type for common access methods to balance amounts. +type SimsAccountBalance struct { + sdk.Coins + owner *SimAccount + r *rand.Rand +} + +// NewSimsAccountBalance constructor +func NewSimsAccountBalance(o *SimAccount, r *rand.Rand, coins sdk.Coins) *SimsAccountBalance { + return &SimsAccountBalance{Coins: coins, r: r, owner: o} +} + +type CoinsFilter interface { + Accept(c sdk.Coins) bool // returns false to reject +} +type CoinsFilterFn func(c sdk.Coins) bool + +func (f CoinsFilterFn) Accept(c sdk.Coins) bool { + return f(c) +} + +func WithSendEnabledCoins() CoinsFilter { + return statefulCoinsFilterFn(func(s *SimAccount, coins sdk.Coins) bool { + for _, coin := range coins { + if !s.bank.IsSendEnabledDenom(coin.Denom) { + return false + } + } + return true + }) +} + +// filter with context of SimAccount +type statefulCoinsFilter struct { + s *SimAccount + do func(s *SimAccount, c sdk.Coins) bool +} + +// constructor +func statefulCoinsFilterFn(f func(s *SimAccount, c sdk.Coins) bool) CoinsFilter { + return &statefulCoinsFilter{do: f} +} + +func (f statefulCoinsFilter) Accept(c sdk.Coins) bool { + if f.s == nil { + panic("account not set") + } + return f.do(f.s, c) +} + +func (f *statefulCoinsFilter) visit(s *SimAccount) { + f.s = s +} + +var _ visitable = &statefulCoinsFilter{} + +type visitable interface { + visit(s *SimAccount) +} + +// RandSubsetCoins return random amounts from the current balance. When the coins are empty, skip is called on the reporter. +// The amounts are removed from the liquid balance. +func (b *SimsAccountBalance) RandSubsetCoins(reporter SimulationReporter, filters ...CoinsFilter) sdk.Coins { + amount := b.randomAmount(5, reporter, b.Coins, filters...) + b.Coins = b.Coins.Sub(amount...) + if amount.Empty() { + reporter.Skip("got empty amounts") + } + return amount +} + +// RandSubsetCoin return random amount from the current balance. When the coins are empty, skip is called on the reporter. +// The amount is removed from the liquid balance. +func (b *SimsAccountBalance) RandSubsetCoin(reporter SimulationReporter, denom string, filters ...CoinsFilter) sdk.Coin { + ok, coin := b.Find(denom) + if !ok { + reporter.Skipf("no such coin: %s", denom) + return sdk.NewCoin(denom, math.ZeroInt()) + } + amounts := b.randomAmount(5, reporter, sdk.Coins{coin}, filters...) + if amounts.Empty() { + reporter.Skip("empty coin") + return sdk.NewCoin(denom, math.ZeroInt()) + } + b.BlockAmount(amounts[0]) + return amounts[0] +} + +// BlockAmount returns true when balance is > requested amount and subtracts the amount from the liquid balance +func (b *SimsAccountBalance) BlockAmount(amount sdk.Coin) bool { + ok, coin := b.Coins.Find(amount.Denom) + if !ok || !coin.IsPositive() || !coin.IsGTE(amount) { + return false + } + b.Coins = b.Coins.Sub(amount) + return true +} + +func (b *SimsAccountBalance) randomAmount(retryCount int, reporter SimulationReporter, coins sdk.Coins, filters ...CoinsFilter) sdk.Coins { + if retryCount < 0 || b.Coins.Empty() { + reporter.Skip("failed to find matching amount") + return sdk.Coins{} + } + amount := simtypes.RandSubsetCoins(b.r, coins) + for _, filter := range filters { + if f, ok := filter.(visitable); ok { + f.visit(b.owner) + } + if !filter.Accept(amount) { + return b.randomAmount(retryCount-1, reporter, coins, filters...) + } + } + return amount +} + +func (b *SimsAccountBalance) RandFees() sdk.Coins { + amount, err := simtypes.RandomFees(b.r, b.Coins) + if err != nil { + return sdk.Coins{} + } + return amount +} + +type SimAccountFilter interface { + // Accept returns true to accept the account or false to reject + Accept(a SimAccount) bool +} +type SimAccountFilterFn func(a SimAccount) bool + +func (s SimAccountFilterFn) Accept(a SimAccount) bool { + return s(a) +} + +func ExcludeAccounts(others ...SimAccount) SimAccountFilter { + return SimAccountFilterFn(func(a SimAccount) bool { + return !slices.ContainsFunc(others, func(o SimAccount) bool { + return a.Address.Equals(o.Address) + }) + }) +} + +// UniqueAccounts returns a stateful filter that rejects duplicate accounts. +// It uses a map to keep track of accounts that have been processed. +// If an account exists in the map, the filter function returns false +// to reject a duplicate, else it adds the account to the map and returns true. +// +// Example usage: +// +// uniqueAccountsFilter := simsx.UniqueAccounts() +// +// for { +// from := testData.AnyAccount(reporter, uniqueAccountsFilter) +// //... rest of the loop +// } +func UniqueAccounts() SimAccountFilter { + idx := make(map[string]struct{}) + return SimAccountFilterFn(func(a SimAccount) bool { + if _, ok := idx[a.AddressBech32]; ok { + return false + } + idx[a.AddressBech32] = struct{}{} + return true + }) +} + +func ExcludeAddresses(addrs ...string) SimAccountFilter { + return SimAccountFilterFn(func(a SimAccount) bool { + return !slices.Contains(addrs, a.AddressBech32) + }) +} + +func WithDenomBalance(denom string) SimAccountFilter { + return SimAccountFilterFn(func(a SimAccount) bool { + return a.LiquidBalance().AmountOf(denom).IsPositive() + }) +} + +func WithLiquidBalanceGTE(amount ...sdk.Coin) SimAccountFilter { + return SimAccountFilterFn(func(a SimAccount) bool { + return a.LiquidBalance().IsAllGTE(amount) + }) +} + +// WithSpendableBalance Filters for liquid token but send may not be enabled for all or any +func WithSpendableBalance() SimAccountFilter { + return SimAccountFilterFn(func(a SimAccount) bool { + return !a.LiquidBalance().Empty() + }) +} + +type ModuleAccountSource interface { + GetModuleAddress(moduleName string) sdk.AccAddress +} + +// BalanceSource is an interface for retrieving balance-related information for a given account. +type BalanceSource interface { + SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins + IsSendEnabledDenom(ctx context.Context, denom string) bool +} + +// ChainDataSource provides common sims test data and helper methods +type ChainDataSource struct { + r *rand.Rand + addressToAccountsPosIndex map[string]int + accounts []SimAccount + accountSource ModuleAccountSource + addressCodec address.Codec + bank contextAwareBalanceSource +} + +// NewChainDataSource constructor +func NewChainDataSource( + ctx context.Context, + r *rand.Rand, + ak ModuleAccountSource, + bk BalanceSource, + codec address.Codec, + oldSimAcc ...simtypes.Account, +) *ChainDataSource { + acc := make([]SimAccount, len(oldSimAcc)) + index := make(map[string]int, len(oldSimAcc)) + bank := contextAwareBalanceSource{ctx: ctx, bank: bk} + for i, a := range oldSimAcc { + acc[i] = SimAccount{Account: a, r: r, bank: bank} + index[a.AddressBech32] = i + } + return &ChainDataSource{ + r: r, + accountSource: ak, + addressCodec: codec, + accounts: acc, + bank: bank, + addressToAccountsPosIndex: index, + } +} + +// AnyAccount returns a random SimAccount matching the filter criteria. Module accounts are excluded. +// In case of an error or no matching account found, the reporter is set to skip and an empty value is returned. +func (c *ChainDataSource) AnyAccount(r SimulationReporter, filters ...SimAccountFilter) SimAccount { + acc := c.randomAccount(r, 5, filters...) + return acc +} + +// GetAccountbyAccAddr return SimAccount with given binary address. Reporter skip flag is set when not found. +func (c ChainDataSource) GetAccountbyAccAddr(reporter SimulationReporter, addr sdk.AccAddress) SimAccount { + if len(addr) == 0 { + reporter.Skip("can not find account for empty address") + return c.nullAccount() + } + addrStr, err := c.addressCodec.BytesToString(addr) + if err != nil { + reporter.Skipf("can not convert account address to string: %s", err) + return c.nullAccount() + } + return c.GetAccount(reporter, addrStr) +} + +func (c ChainDataSource) HasAccount(addr string) bool { + _, ok := c.addressToAccountsPosIndex[addr] + return ok +} + +// GetAccount return SimAccount with given bench32 address. Reporter skip flag is set when not found. +func (c ChainDataSource) GetAccount(reporter SimulationReporter, addr string) SimAccount { + pos, ok := c.addressToAccountsPosIndex[addr] + if !ok { + reporter.Skipf("no account: %s", addr) + return c.nullAccount() + } + return c.accounts[pos] +} + +func (c *ChainDataSource) randomAccount(reporter SimulationReporter, retryCount int, filters ...SimAccountFilter) SimAccount { + if retryCount < 0 { + reporter.Skip("failed to find a matching account") + return c.nullAccount() + } + idx := c.r.Intn(len(c.accounts)) + acc := c.accounts[idx] + for _, filter := range filters { + if !filter.Accept(acc) { + return c.randomAccount(reporter, retryCount-1, filters...) + } + } + return acc +} + +// create null object +func (c ChainDataSource) nullAccount() SimAccount { + return SimAccount{ + Account: simtypes.Account{}, + r: c.r, + liquidBalance: &SimsAccountBalance{}, + bank: c.accounts[0].bank, + } +} + +func (c *ChainDataSource) ModuleAccountAddress(reporter SimulationReporter, moduleName string) string { + acc := c.accountSource.GetModuleAddress(moduleName) + if acc == nil { + reporter.Skipf("unknown module account: %s", moduleName) + return "" + } + res, err := c.addressCodec.BytesToString(acc) + if err != nil { + reporter.Skipf("failed to encode module address: %s", err) + return "" + } + return res +} + +func (c *ChainDataSource) AddressCodec() address.Codec { + return c.addressCodec +} + +func (c *ChainDataSource) Rand() *XRand { + return &XRand{c.r} +} + +func (c *ChainDataSource) IsSendEnabledDenom(denom string) bool { + return c.bank.IsSendEnabledDenom(denom) +} + +// AllAccounts returns all accounts in legacy format +func (c *ChainDataSource) AllAccounts() []simtypes.Account { + return Collect(c.accounts, func(a SimAccount) simtypes.Account { return a.Account }) +} + +func (c *ChainDataSource) AccountsCount() int { + return len(c.accounts) +} + +// AccountAt return SimAccount within the accounts slice. Reporter skip flag is set when boundaries are exceeded. + +func (c *ChainDataSource) AccountAt(reporter SimulationReporter, i int) SimAccount { + if i > len(c.accounts) { + reporter.Skipf("account index out of range: %d", i) + return c.nullAccount() + } + return c.accounts[i] +} + +type XRand struct { + *rand.Rand +} + +// NewXRand constructor +func NewXRand(rand *rand.Rand) *XRand { + return &XRand{Rand: rand} +} + +func (r *XRand) StringN(max int) string { + return simtypes.RandStringOfLength(r.Rand, max) +} + +func (r *XRand) SubsetCoins(src sdk.Coins) sdk.Coins { + return simtypes.RandSubsetCoins(r.Rand, src) +} + +// Coin return one coin from the list +func (r *XRand) Coin(src sdk.Coins) sdk.Coin { + return src[r.Intn(len(src))] +} + +func (r *XRand) DecN(max math.LegacyDec) math.LegacyDec { + return simtypes.RandomDecAmount(r.Rand, max) +} + +func (r *XRand) IntInRange(min, max int) int { + return r.Rand.Intn(max-min) + min +} + +// Uint64InRange returns a pseudo-random uint64 number in the range [min, max]. +// It panics when min >= max +func (r *XRand) Uint64InRange(min, max uint64) uint64 { + return uint64(r.Rand.Int63n(int64(max-min)) + int64(min)) +} + +// Uint32InRange returns a pseudo-random uint32 number in the range [min, max]. +// It panics when min >= max +func (r *XRand) Uint32InRange(min, max uint32) uint32 { + return uint32(r.Rand.Intn(int(max-min))) + min +} + +func (r *XRand) PositiveSDKIntn(max math.Int) (math.Int, error) { + return simtypes.RandPositiveInt(r.Rand, max) +} + +func (r *XRand) PositiveSDKIntInRange(min, max math.Int) (math.Int, error) { + diff := max.Sub(min) + if !diff.IsPositive() { + return math.Int{}, errors.New("min value must not be greater or equal to max") + } + result, err := r.PositiveSDKIntn(diff) + if err != nil { + return math.Int{}, err + } + return result.Add(min), nil +} + +// Timestamp returns a timestamp between Jan 1, 2062 and Jan 1, 2262 +func (r *XRand) Timestamp() time.Time { + return simtypes.RandTimestamp(r.Rand) +} + +func (r *XRand) Bool() bool { + return r.Intn(100) > 50 +} + +func (r *XRand) Amount(balance math.Int) math.Int { + return simtypes.RandomAmount(r.Rand, balance) +} diff --git a/simsx/environment_test.go b/simsx/environment_test.go new file mode 100644 index 000000000000..957601326b23 --- /dev/null +++ b/simsx/environment_test.go @@ -0,0 +1,67 @@ +package simsx + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" + + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +func TestChainDataSourceAnyAccount(t *testing.T) { + codec := txConfig().SigningContext().AddressCodec() + r := rand.New(rand.NewSource(1)) + accs := simtypes.RandomAccounts(r, 3) + specs := map[string]struct { + filters []SimAccountFilter + assert func(t *testing.T, got SimAccount, reporter SimulationReporter) + }{ + "no filters": { + assert: func(t *testing.T, got SimAccount, reporter SimulationReporter) { //nolint:thelper // not a helper + assert.NotEmpty(t, got.AddressBech32) + assert.False(t, reporter.IsSkipped()) + }, + }, + "filter": { + filters: []SimAccountFilter{SimAccountFilterFn(func(a SimAccount) bool { return a.AddressBech32 == accs[2].AddressBech32 })}, + assert: func(t *testing.T, got SimAccount, reporter SimulationReporter) { //nolint:thelper // not a helper + assert.Equal(t, accs[2].AddressBech32, got.AddressBech32) + assert.False(t, reporter.IsSkipped()) + }, + }, + "no match": { + filters: []SimAccountFilter{SimAccountFilterFn(func(a SimAccount) bool { return false })}, + assert: func(t *testing.T, got SimAccount, reporter SimulationReporter) { //nolint:thelper // not a helper + assert.Empty(t, got.AddressBech32) + assert.True(t, reporter.IsSkipped()) + }, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + reporter := NewBasicSimulationReporter() + c := NewChainDataSource(sdk.Context{}, r, nil, nil, codec, accs...) + a := c.AnyAccount(reporter, spec.filters...) + spec.assert(t, a, reporter) + }) + } +} + +func TestChainDataSourceGetHasAccount(t *testing.T) { + codec := txConfig().SigningContext().AddressCodec() + r := rand.New(rand.NewSource(1)) + accs := simtypes.RandomAccounts(r, 3) + reporter := NewBasicSimulationReporter() + c := NewChainDataSource(sdk.Context{}, r, nil, nil, codec, accs...) + exisingAddr := accs[0].AddressBech32 + assert.Equal(t, exisingAddr, c.GetAccount(reporter, exisingAddr).AddressBech32) + assert.False(t, reporter.IsSkipped()) + assert.True(t, c.HasAccount(exisingAddr)) + // and non-existing account + reporter = NewBasicSimulationReporter() + assert.Empty(t, c.GetAccount(reporter, "non-existing").AddressBech32) + assert.False(t, c.HasAccount("non-existing")) + assert.True(t, reporter.IsSkipped()) +} diff --git a/simsx/msg_factory.go b/simsx/msg_factory.go new file mode 100644 index 000000000000..1a7c880a17a1 --- /dev/null +++ b/simsx/msg_factory.go @@ -0,0 +1,151 @@ +package simsx + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type SimMsgFactoryX interface { + MsgType() sdk.Msg + Create() FactoryMethod + DeliveryResultHandler() SimDeliveryResultHandler +} +type ( + // FactoryMethod method signature that is implemented by the concrete message factories + FactoryMethod func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg sdk.Msg) + + // FactoryMethodWithFutureOps extended message factory method for the gov module or others that have to schedule operations for a future block. + FactoryMethodWithFutureOps[T sdk.Msg] func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter, fOpsReg FutureOpsRegistry) ([]SimAccount, T) + + // FactoryMethodWithDeliveryResultHandler extended factory method that can return a result handler, that is executed on the delivery tx error result. + // This is used in staking for example to validate negative execution results. + FactoryMethodWithDeliveryResultHandler[T sdk.Msg] func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg T, handler SimDeliveryResultHandler) +) + +var _ SimMsgFactoryX = &ResultHandlingSimMsgFactory[sdk.Msg]{} + +// ResultHandlingSimMsgFactory message factory with a delivery error result handler configured. +type ResultHandlingSimMsgFactory[T sdk.Msg] struct { + SimMsgFactoryFn[T] + resultHandler SimDeliveryResultHandler +} + +// NewSimMsgFactoryWithDeliveryResultHandler constructor +func NewSimMsgFactoryWithDeliveryResultHandler[T sdk.Msg](f FactoryMethodWithDeliveryResultHandler[T]) *ResultHandlingSimMsgFactory[T] { + // the result handler is always called after the factory. so we initialize it lazy for syntactic sugar and simplicity + // in the message factory function that is implemented by the users + var lazyResultHandler SimDeliveryResultHandler + r := &ResultHandlingSimMsgFactory[T]{ + resultHandler: func(err error) error { + return lazyResultHandler(err) + }, + } + r.SimMsgFactoryFn = func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg T) { + signer, msg, lazyResultHandler = f(ctx, testData, reporter) + if lazyResultHandler == nil { + lazyResultHandler = expectNoError + } + return + } + return r +} + +func (f ResultHandlingSimMsgFactory[T]) DeliveryResultHandler() SimDeliveryResultHandler { + return f.resultHandler +} + +var ( + _ SimMsgFactoryX = &LazyStateSimMsgFactory[sdk.Msg]{} + _ HasFutureOpsRegistry = &LazyStateSimMsgFactory[sdk.Msg]{} +) + +// LazyStateSimMsgFactory stateful message factory with weighted proposals and future operation +// registry initialized lazy before execution. +type LazyStateSimMsgFactory[T sdk.Msg] struct { + SimMsgFactoryFn[T] + fsOpsReg FutureOpsRegistry +} + +func NewSimMsgFactoryWithFutureOps[T sdk.Msg](f FactoryMethodWithFutureOps[T]) *LazyStateSimMsgFactory[T] { + r := &LazyStateSimMsgFactory[T]{} + r.SimMsgFactoryFn = func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg T) { + signer, msg = f(ctx, testData, reporter, r.fsOpsReg) + return + } + return r +} + +func (c *LazyStateSimMsgFactory[T]) SetFutureOpsRegistry(registry FutureOpsRegistry) { + c.fsOpsReg = registry +} + +// pass errors through and don't handle them +func expectNoError(err error) error { + return err +} + +var _ SimMsgFactoryX = SimMsgFactoryFn[sdk.Msg](nil) + +// SimMsgFactoryFn is the default factory for most cases. It does not create future operations but ensures successful message delivery. +type SimMsgFactoryFn[T sdk.Msg] func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg T) + +// MsgType returns an empty instance of type T, which implements `sdk.Msg`. +func (f SimMsgFactoryFn[T]) MsgType() sdk.Msg { + var x T + return x +} + +func (f SimMsgFactoryFn[T]) Create() FactoryMethod { + // adapter to return sdk.Msg instead of typed result to match FactoryMethod signature + return func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) ([]SimAccount, sdk.Msg) { + return f(ctx, testData, reporter) + } +} + +func (f SimMsgFactoryFn[T]) DeliveryResultHandler() SimDeliveryResultHandler { + return expectNoError +} + +func (f SimMsgFactoryFn[T]) Cast(msg sdk.Msg) T { + return msg.(T) +} + +type tuple struct { + signer []SimAccount + msg sdk.Msg +} + +// SafeRunFactoryMethod runs the factory method on a separate goroutine to abort early when the context is canceled via reporter skip +func SafeRunFactoryMethod( + ctx context.Context, + data *ChainDataSource, + reporter SimulationReporter, + f FactoryMethod, +) (signer []SimAccount, msg sdk.Msg) { + r := make(chan tuple) + go func() { + defer recoverPanicForSkipped(reporter, r) + signer, msg := f(ctx, data, reporter) + r <- tuple{signer: signer, msg: msg} + }() + select { + case t, ok := <-r: + if !ok { + return nil, nil + } + return t.signer, t.msg + case <-ctx.Done(): + reporter.Skip("context closed") + return nil, nil + } +} + +func recoverPanicForSkipped(reporter SimulationReporter, resultChan chan tuple) { + if r := recover(); r != nil { + if !reporter.IsSkipped() { + panic(r) + } + close(resultChan) + } +} diff --git a/simsx/msg_factory_test.go b/simsx/msg_factory_test.go new file mode 100644 index 000000000000..d5cadb222b69 --- /dev/null +++ b/simsx/msg_factory_test.go @@ -0,0 +1,97 @@ +package simsx + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestMsgFactories(t *testing.T) { + myMsg := testdata.NewTestMsg() + mySigners := []SimAccount{{}} + specs := map[string]struct { + src SimMsgFactoryX + expErrHandled bool + }{ + "default": { + src: SimMsgFactoryFn[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg *testdata.TestMsg) { + return mySigners, myMsg + }), + }, + "with delivery result handler": { + src: NewSimMsgFactoryWithDeliveryResultHandler[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg *testdata.TestMsg, handler SimDeliveryResultHandler) { + return mySigners, myMsg, func(err error) error { return nil } + }), + expErrHandled: true, + }, + "with future ops": { + src: NewSimMsgFactoryWithFutureOps[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter, fOpsReg FutureOpsRegistry) ([]SimAccount, *testdata.TestMsg) { + return mySigners, myMsg + }), + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + assert.Equal(t, (*testdata.TestMsg)(nil), spec.src.MsgType()) + + factoryMethod := spec.src.Create() + require.NotNil(t, factoryMethod) + gotSigners, gotMsg := factoryMethod(context.Background(), nil, nil) + assert.Equal(t, mySigners, gotSigners) + assert.Equal(t, gotMsg, myMsg) + + require.NotNil(t, spec.src.DeliveryResultHandler()) + gotErr := spec.src.DeliveryResultHandler()(errors.New("testing")) + assert.Equal(t, spec.expErrHandled, gotErr == nil) + }) + } +} + +func TestRunWithFailFast(t *testing.T) { + myTestMsg := testdata.NewTestMsg() + mySigners := []SimAccount{SimAccountFixture()} + specs := map[string]struct { + factory FactoryMethod + expSigners []SimAccount + expMsg sdk.Msg + expSkipped bool + }{ + "factory completes": { + factory: func(ctx context.Context, _ *ChainDataSource, reporter SimulationReporter) ([]SimAccount, sdk.Msg) { + return mySigners, myTestMsg + }, + expSigners: mySigners, + expMsg: myTestMsg, + }, + "factory skips": { + factory: func(ctx context.Context, _ *ChainDataSource, reporter SimulationReporter) ([]SimAccount, sdk.Msg) { + reporter.Skip("testing") + return nil, nil + }, + expSkipped: true, + }, + "factory skips and panics": { + factory: func(ctx context.Context, _ *ChainDataSource, reporter SimulationReporter) ([]SimAccount, sdk.Msg) { + reporter.Skip("testing") + panic("should be handled") + }, + expSkipped: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + ctx, done := context.WithCancel(context.Background()) + reporter := NewBasicSimulationReporter().WithScope(&testdata.TestMsg{}, SkipHookFn(func(...any) { done() })) + gotSigners, gotMsg := SafeRunFactoryMethod(ctx, nil, reporter, spec.factory) + assert.Equal(t, spec.expSigners, gotSigners) + assert.Equal(t, spec.expMsg, gotMsg) + assert.Equal(t, spec.expSkipped, reporter.IsSkipped()) + }) + } +} diff --git a/simsx/params.go b/simsx/params.go new file mode 100644 index 000000000000..7acd6edcf84f --- /dev/null +++ b/simsx/params.go @@ -0,0 +1,52 @@ +package simsx + +import ( + "math/rand" + + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +// WeightSource interface for retrieving weights based on a name and a default value. +type WeightSource interface { + Get(name string, defaultValue uint32) uint32 +} + +// WeightSourceFn function adapter that implements WeightSource. +// Example: +// +// weightSource := WeightSourceFn(func(name string, defaultValue uint32) uint32 { +// // implementation code... +// }) +type WeightSourceFn func(name string, defaultValue uint32) uint32 + +func (f WeightSourceFn) Get(name string, defaultValue uint32) uint32 { + return f(name, defaultValue) +} + +// ParamWeightSource is an adapter to the simtypes.AppParams object. This function returns a WeightSource +// implementation that retrieves weights +// based on a name and a default value. The implementation uses the provided AppParams +// to get or generate the weight value. If the weight value exists in the AppParams, +// it is decoded and returned. Otherwise, the provided ParamSimulator is used to generate +// a random value or default value. +// +// The WeightSource implementation is a WeightSourceFn function adapter that implements +// the WeightSource interface. It takes in a name string and a defaultValue uint32 as +// parameters and returns the weight value as a uint32. +// +// Example Usage: +// +// appParams := simtypes.AppParams{} +// // add parameters to appParams +// +// weightSource := ParamWeightSource(appParams) +// weightSource.Get("some_weight", 100) +func ParamWeightSource(p simtypes.AppParams) WeightSource { + return WeightSourceFn(func(name string, defaultValue uint32) uint32 { + var result uint32 + p.GetOrGenerate("op_weight_"+name, &result, nil, func(_ *rand.Rand) { + result = defaultValue + }) + return result + }) +} diff --git a/simsx/registry.go b/simsx/registry.go new file mode 100644 index 000000000000..ce2e34bb29f9 --- /dev/null +++ b/simsx/registry.go @@ -0,0 +1,206 @@ +package simsx + +import ( + "context" + "iter" + "math/rand" + "slices" + "strings" + "time" + + "golang.org/x/exp/maps" + + "cosmossdk.io/core/address" + "cosmossdk.io/core/log" + + "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +type ( + // Registry is an abstract entry point to register message factories with weights + Registry interface { + Add(weight uint32, f SimMsgFactoryX) + } + // FutureOpsRegistry register message factories for future blocks + FutureOpsRegistry interface { + Add(blockTime time.Time, f SimMsgFactoryX) + } + + // AccountSourceX Account and Module account + AccountSourceX interface { + AccountSource + ModuleAccountSource + } +) + +// WeightedProposalMsgIter iterator for weighted gov proposal payload messages +type WeightedProposalMsgIter = iter.Seq2[uint32, SimMsgFactoryX] + +var _ Registry = &WeightedOperationRegistryAdapter{} + +// common types for abstract registry without generics +type regCommon struct { + reporter SimulationReporter + ak AccountSourceX + bk BalanceSource + addressCodec address.Codec + txConfig client.TxConfig + logger log.Logger +} + +func (c regCommon) newChainDataSource(ctx context.Context, r *rand.Rand, accs ...simtypes.Account) *ChainDataSource { + return NewChainDataSource(ctx, r, c.ak, c.bk, c.addressCodec, accs...) +} + +type AbstractRegistry[T any] struct { + regCommon + legacyObjs []T +} + +// ToLegacyObjects returns the legacy properties of the SimsRegistryAdapter as a slice of type T. +func (l *AbstractRegistry[T]) ToLegacyObjects() []T { + return l.legacyObjs +} + +// WeightedOperationRegistryAdapter is an implementation of the Registry interface that provides adapters to use the new message factories +// with the legacy simulation system +type WeightedOperationRegistryAdapter struct { + AbstractRegistry[simtypes.WeightedOperation] +} + +// NewSimsMsgRegistryAdapter creates a new instance of SimsRegistryAdapter for WeightedOperation types. +func NewSimsMsgRegistryAdapter( + reporter SimulationReporter, + ak AccountSourceX, + bk BalanceSource, + txConfig client.TxConfig, + logger log.Logger, +) *WeightedOperationRegistryAdapter { + return &WeightedOperationRegistryAdapter{ + AbstractRegistry: AbstractRegistry[simtypes.WeightedOperation]{ + regCommon: regCommon{ + reporter: reporter, + ak: ak, + bk: bk, + txConfig: txConfig, + addressCodec: txConfig.SigningContext().AddressCodec(), + logger: logger, + }, + }, + } +} + +// Add adds a new weighted operation to the collection +func (l *WeightedOperationRegistryAdapter) Add(weight uint32, fx SimMsgFactoryX) { + if fx == nil { + panic("message factory must not be nil") + } + if weight == 0 { + return + } + obj := simulation.NewWeightedOperation(int(weight), legacyOperationAdapter(l.regCommon, fx)) + l.legacyObjs = append(l.legacyObjs, obj) +} + +type HasFutureOpsRegistry interface { + SetFutureOpsRegistry(FutureOpsRegistry) +} + +func legacyOperationAdapter(l regCommon, fx SimMsgFactoryX) simtypes.Operation { + return func( + r *rand.Rand, app AppEntrypoint, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + xCtx, done := context.WithCancel(ctx) + ctx = sdk.UnwrapSDKContext(xCtx) + testData := l.newChainDataSource(ctx, r, accs...) + reporter := l.reporter.WithScope(fx.MsgType(), SkipHookFn(func(args ...any) { done() })) + fOpsReg := NewFutureOpsRegistry(l) + if fx, ok := fx.(HasFutureOpsRegistry); ok { + fx.SetFutureOpsRegistry(fOpsReg) + } + from, msg := SafeRunFactoryMethod(ctx, testData, reporter, fx.Create()) + futOps := fOpsReg.legacyObjs + weightedOpsResult := DeliverSimsMsg(ctx, reporter, app, r, l.txConfig, l.ak, chainID, msg, fx.DeliveryResultHandler(), from...) + err := reporter.Close() + return weightedOpsResult, futOps, err + } +} + +func NewFutureOpsRegistry(l regCommon) *FutureOperationRegistryAdapter { + return &FutureOperationRegistryAdapter{regCommon: l} +} + +type FutureOperationRegistryAdapter AbstractRegistry[simtypes.FutureOperation] + +func (l *FutureOperationRegistryAdapter) Add(blockTime time.Time, fx SimMsgFactoryX) { + if fx == nil { + panic("message factory must not be nil") + } + if blockTime.IsZero() { + return + } + obj := simtypes.FutureOperation{ + BlockTime: blockTime, + Op: legacyOperationAdapter(l.regCommon, fx), + } + l.legacyObjs = append(l.legacyObjs, obj) +} + +var _ Registry = &UniqueTypeRegistry{} + +type UniqueTypeRegistry map[string]WeightedFactory + +func NewUniqueTypeRegistry() UniqueTypeRegistry { + return make(UniqueTypeRegistry) +} + +func (s UniqueTypeRegistry) Add(weight uint32, f SimMsgFactoryX) { + msgType := f.MsgType() + msgTypeURL := sdk.MsgTypeURL(msgType) + if _, exists := s[msgTypeURL]; exists { + panic("type is already registered: " + msgTypeURL) + } + s[msgTypeURL] = WeightedFactory{Weight: weight, Factory: f} +} + +// Iterator returns an iterator function for a Go for loop sorted by weight desc. +func (s UniqueTypeRegistry) Iterator() iter.Seq2[uint32, SimMsgFactoryX] { + x := maps.Values(s) + slices.SortFunc(x, func(a, b WeightedFactory) int { + return a.Compare(b) + }) + return func(yield func(uint32, SimMsgFactoryX) bool) { + for _, v := range x { + if !yield(v.Weight, v.Factory) { + return + } + } + } +} + +// WeightedFactory is a Weight Factory tuple +type WeightedFactory struct { + Weight uint32 + Factory SimMsgFactoryX +} + +// Compare compares the WeightedFactory f with another WeightedFactory b. +// The comparison is done by comparing the weight of f with the weight of b. +// If the weight of f is greater than the weight of b, it returns 1. +// If the weight of f is less than the weight of b, it returns -1. +// If the weights are equal, it compares the TypeURL of the factories using strings.Compare. +// Returns an integer indicating the comparison result. +func (f WeightedFactory) Compare(b WeightedFactory) int { + switch { + case f.Weight > b.Weight: + return 1 + case f.Weight < b.Weight: + return -1 + default: + return strings.Compare(sdk.MsgTypeURL(f.Factory.MsgType()), sdk.MsgTypeURL(b.Factory.MsgType())) + } +} diff --git a/simsx/registry_test.go b/simsx/registry_test.go new file mode 100644 index 000000000000..da6b1f0e2e1b --- /dev/null +++ b/simsx/registry_test.go @@ -0,0 +1,158 @@ +package simsx + +import ( + "context" + "errors" + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" + + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +func TestSimsMsgRegistryAdapter(t *testing.T) { + senderAcc := SimAccountFixture() + accs := []simtypes.Account{senderAcc.Account} + ak := MockAccountSourceX{GetAccountFn: MemoryAccountSource(senderAcc).GetAccount} + myMsg := testdata.NewTestMsg(senderAcc.Address) + ctx := sdk.Context{}.WithContext(context.Background()) + futureTime := time.Now().Add(time.Second) + + specs := map[string]struct { + factory SimMsgFactoryX + expFactoryMsg sdk.Msg + expFactoryErr error + expDeliveryErr error + expFutureOpsCount int + }{ + "successful execution": { + factory: SimMsgFactoryFn[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg *testdata.TestMsg) { + return []SimAccount{senderAcc}, myMsg + }), + expFactoryMsg: myMsg, + }, + "skip execution": { + factory: SimMsgFactoryFn[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg *testdata.TestMsg) { + reporter.Skip("testing") + return nil, nil + }), + }, + "future ops registration": { + factory: NewSimMsgFactoryWithFutureOps[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter, fOpsReg FutureOpsRegistry) (signer []SimAccount, msg *testdata.TestMsg) { + fOpsReg.Add(futureTime, SimMsgFactoryFn[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg *testdata.TestMsg) { + return []SimAccount{senderAcc}, myMsg + })) + return []SimAccount{senderAcc}, myMsg + }), + expFactoryMsg: myMsg, + expFutureOpsCount: 1, + }, + "error in factory execution": { + factory: NewSimMsgFactoryWithFutureOps[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter, fOpsReg FutureOpsRegistry) (signer []SimAccount, msg *testdata.TestMsg) { + reporter.Fail(errors.New("testing")) + return nil, nil + }), + expFactoryErr: errors.New("testing"), + }, + "missing senders": { + factory: SimMsgFactoryFn[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg *testdata.TestMsg) { + return nil, myMsg + }), + expDeliveryErr: errors.New("no senders"), + }, + "error in delivery execution": { + factory: SimMsgFactoryFn[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg *testdata.TestMsg) { + return []SimAccount{senderAcc}, myMsg + }), + expDeliveryErr: errors.New("testing"), + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + r := NewBasicSimulationReporter() + reg := NewSimsMsgRegistryAdapter(r, ak, nil, txConfig(), log.NewNopLogger()) + // when + reg.Add(100, spec.factory) + // then + gotOps := reg.ToLegacyObjects() + require.Len(t, gotOps, 1) + assert.Equal(t, 100, gotOps[0].Weight()) + + // and when ops executed + var capturedTXs []sdk.Tx + captureTXApp := AppEntrypointFn(func(_txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { + capturedTXs = append(capturedTXs, tx) + return sdk.GasInfo{}, &sdk.Result{}, spec.expDeliveryErr + }) + fn := gotOps[0].Op() + gotOpsResult, gotFOps, gotErr := fn(rand.New(rand.NewSource(1)), captureTXApp, ctx, accs, "testchain") + // then + if spec.expFactoryErr != nil { + require.Equal(t, spec.expFactoryErr, gotErr) + assert.Empty(t, gotFOps) + assert.Equal(t, gotOpsResult.OK, spec.expFactoryErr == nil) + assert.Empty(t, gotOpsResult.Comment) + require.Empty(t, capturedTXs) + } + if spec.expDeliveryErr != nil { + require.Equal(t, spec.expDeliveryErr, gotErr) + } + // and verify TX delivery + if spec.expFactoryMsg != nil { + require.Len(t, capturedTXs, 1) + require.Len(t, capturedTXs[0].GetMsgs(), 1) + assert.Equal(t, spec.expFactoryMsg, capturedTXs[0].GetMsgs()[0]) + } + assert.Len(t, gotFOps, spec.expFutureOpsCount) + }) + } +} + +func TestUniqueTypeRegistry(t *testing.T) { + f1 := SimMsgFactoryFn[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg *testdata.TestMsg) { + return []SimAccount{}, nil + }) + specs := map[string]struct { + src []WeightedFactory + exp []WeightedFactory + expErr bool + }{ + "unique": { + src: []WeightedFactory{{Weight: 1, Factory: f1}}, + exp: []WeightedFactory{{Weight: 1, Factory: f1}}, + }, + "duplicate": { + src: []WeightedFactory{{Weight: 1, Factory: f1}, {Weight: 2, Factory: f1}}, + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + reg := NewUniqueTypeRegistry() + if spec.expErr { + require.Panics(t, func() { + for _, v := range spec.src { + reg.Add(v.Weight, v.Factory) + } + }) + return + } + for _, v := range spec.src { + reg.Add(v.Weight, v.Factory) + } + // then + var got []WeightedFactory + for w, f := range reg.Iterator() { + got = append(got, WeightedFactory{Weight: w, Factory: f}) + } + require.Len(t, got, len(spec.exp)) + }) + } +} diff --git a/simsx/reporter.go b/simsx/reporter.go new file mode 100644 index 000000000000..a70491a31b74 --- /dev/null +++ b/simsx/reporter.go @@ -0,0 +1,258 @@ +package simsx + +import ( + "errors" + "fmt" + "slices" + "sort" + "strings" + "sync" + "sync/atomic" + + "golang.org/x/exp/maps" + + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +// SimulationReporter is an interface for reporting the result of a simulation run. +type SimulationReporter interface { + WithScope(msg sdk.Msg, optionalSkipHook ...SkipHook) SimulationReporter + Skip(comment string) + Skipf(comment string, args ...any) + // IsSkipped returns true when skipped or completed + IsSkipped() bool + ToLegacyOperationMsg() simtypes.OperationMsg + // Fail complete with failure + Fail(err error, comments ...string) + // Success complete with success + Success(msg sdk.Msg, comments ...string) + // Close returns error captured on fail + Close() error + Comment() string +} + +var _ SimulationReporter = &BasicSimulationReporter{} + +type ReporterStatus uint8 + +const ( + undefined ReporterStatus = iota + skipped ReporterStatus = iota + completed ReporterStatus = iota +) + +func (s ReporterStatus) String() string { + switch s { + case skipped: + return "skipped" + case completed: + return "completed" + default: + return "undefined" + } +} + +// SkipHook is an interface that represents a callback hook used triggered on skip operations. +// It provides a single method `Skip` that accepts variadic arguments. This interface is implemented +// by Go stdlib testing.T and testing.B +type SkipHook interface { + Skip(args ...any) +} + +var _ SkipHook = SkipHookFn(nil) + +type SkipHookFn func(args ...any) + +func (s SkipHookFn) Skip(args ...any) { + s(args...) +} + +type BasicSimulationReporter struct { + skipCallbacks []SkipHook + completedCallback func(reporter *BasicSimulationReporter) + module string + msgTypeURL string + + status atomic.Uint32 + + cMX sync.RWMutex + comments []string + error error + + summary *ExecutionSummary +} + +// NewBasicSimulationReporter constructor that accepts an optional callback hook that is called on state transition to skipped status +// A typical implementation for this hook is testing.T or testing.B. +func NewBasicSimulationReporter(optionalSkipHook ...SkipHook) *BasicSimulationReporter { + r := &BasicSimulationReporter{ + skipCallbacks: optionalSkipHook, + summary: NewExecutionSummary(), + } + r.completedCallback = func(child *BasicSimulationReporter) { + r.summary.Add(child.module, child.msgTypeURL, ReporterStatus(child.status.Load()), child.Comment()) + } + return r +} + +// WithScope is a method of the BasicSimulationReporter type that creates a new instance of SimulationReporter +// with an additional scope specified by the input `msg`. The msg is used to set type, module and binary data as +// context for the legacy operation. +// The WithScope method acts as a constructor to initialize state and has to be called before using the instance +// in DeliverSimsMsg. +// +// The method accepts an optional `optionalSkipHook` parameter +// that can be used to add a callback hook that is triggered on skip operations additional to any parent skip hook. +// This method returns the newly created +// SimulationReporter instance. +func (x *BasicSimulationReporter) WithScope(msg sdk.Msg, optionalSkipHook ...SkipHook) SimulationReporter { + typeURL := sdk.MsgTypeURL(msg) + r := &BasicSimulationReporter{ + skipCallbacks: append(x.skipCallbacks, optionalSkipHook...), + completedCallback: x.completedCallback, + error: x.error, + msgTypeURL: typeURL, + module: sdk.GetModuleNameFromTypeURL(typeURL), + comments: slices.Clone(x.comments), + } + r.status.Store(x.status.Load()) + return r +} + +func (x *BasicSimulationReporter) Skip(comment string) { + x.toStatus(skipped, comment) +} + +func (x *BasicSimulationReporter) Skipf(comment string, args ...any) { + x.Skip(fmt.Sprintf(comment, args...)) +} + +func (x *BasicSimulationReporter) IsSkipped() bool { + return ReporterStatus(x.status.Load()) > undefined +} + +func (x *BasicSimulationReporter) ToLegacyOperationMsg() simtypes.OperationMsg { + switch ReporterStatus(x.status.Load()) { + case skipped: + return simtypes.NoOpMsg(x.module, x.msgTypeURL, x.Comment()) + case completed: + x.cMX.RLock() + err := x.error + x.cMX.RUnlock() + if err == nil { + return simtypes.NewOperationMsgBasic(x.module, x.msgTypeURL, x.Comment(), true) + } else { + return simtypes.NewOperationMsgBasic(x.module, x.msgTypeURL, x.Comment(), false) + } + default: + x.Fail(errors.New("operation aborted before msg was executed")) + return x.ToLegacyOperationMsg() + } +} + +func (x *BasicSimulationReporter) Fail(err error, comments ...string) { + if !x.toStatus(completed, comments...) { + return + } + x.cMX.Lock() + defer x.cMX.Unlock() + x.error = err +} + +func (x *BasicSimulationReporter) Success(msg sdk.Msg, comments ...string) { + if !x.toStatus(completed, comments...) { + return + } + if msg == nil { + return + } +} + +func (x *BasicSimulationReporter) Close() error { + x.completedCallback(x) + x.cMX.RLock() + defer x.cMX.RUnlock() + return x.error +} + +func (x *BasicSimulationReporter) toStatus(next ReporterStatus, comments ...string) bool { + oldStatus := ReporterStatus(x.status.Load()) + if oldStatus > next { + panic(fmt.Sprintf("can not switch from status %s to %s", oldStatus, next)) + } + if !x.status.CompareAndSwap(uint32(oldStatus), uint32(next)) { + return false + } + x.cMX.Lock() + newComments := append(x.comments, comments...) + x.comments = newComments + x.cMX.Unlock() + + if oldStatus != skipped && next == skipped { + prettyComments := strings.Join(newComments, ", ") + for _, hook := range x.skipCallbacks { + hook.Skip(prettyComments) + } + } + return true +} + +func (x *BasicSimulationReporter) Comment() string { + x.cMX.RLock() + defer x.cMX.RUnlock() + return strings.Join(x.comments, ", ") +} + +func (x *BasicSimulationReporter) Summary() *ExecutionSummary { + return x.summary +} + +type ExecutionSummary struct { + mx sync.RWMutex + counts map[string]int // module to count + skipReasons map[string]map[string]int // msg type to reason->count +} + +func NewExecutionSummary() *ExecutionSummary { + return &ExecutionSummary{counts: make(map[string]int), skipReasons: make(map[string]map[string]int)} +} + +func (s *ExecutionSummary) Add(module, url string, status ReporterStatus, comment string) { + s.mx.Lock() + defer s.mx.Unlock() + combinedKey := fmt.Sprintf("%s_%s", module, status.String()) + s.counts[combinedKey] += 1 + if status == completed { + return + } + r, ok := s.skipReasons[url] + if !ok { + r = make(map[string]int) + s.skipReasons[url] = r + } + r[comment] += 1 +} + +func (s *ExecutionSummary) String() string { + s.mx.RLock() + defer s.mx.RUnlock() + keys := maps.Keys(s.counts) + sort.Strings(keys) + var sb strings.Builder + for _, key := range keys { + sb.WriteString(fmt.Sprintf("%s: %d\n", key, s.counts[key])) + } + for m, c := range s.skipReasons { + sb.WriteString(fmt.Sprintf("%d\t%s: %q\n", sum(maps.Values(c)), m, maps.Keys(c))) + } + return sb.String() +} + +func sum(values []int) int { + var r int + for _, v := range values { + r += v + } + return r +} diff --git a/simsx/reporter_test.go b/simsx/reporter_test.go new file mode 100644 index 000000000000..2bc47fe902b0 --- /dev/null +++ b/simsx/reporter_test.go @@ -0,0 +1,219 @@ +package simsx + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/testutil/testdata" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +func TestSimulationReporterToLegacy(t *testing.T) { + myErr := errors.New("my-error") + myMsg := testdata.NewTestMsg([]byte{1}) + + specs := map[string]struct { + setup func() SimulationReporter + expOp simtypes.OperationMsg + expErr error + }{ + "init only": { + setup: func() SimulationReporter { return NewBasicSimulationReporter() }, + expOp: simtypes.NewOperationMsgBasic("", "", "", false), + expErr: errors.New("operation aborted before msg was executed"), + }, + "success result": { + setup: func() SimulationReporter { + r := NewBasicSimulationReporter().WithScope(myMsg) + r.Success(myMsg, "testing") + return r + }, + expOp: simtypes.NewOperationMsgBasic("TestMsg", "/testpb.TestMsg", "testing", true), + }, + "error result": { + setup: func() SimulationReporter { + r := NewBasicSimulationReporter().WithScope(myMsg) + r.Fail(myErr, "testing") + return r + }, + expOp: simtypes.NewOperationMsgBasic("TestMsg", "/testpb.TestMsg", "testing", false), + expErr: myErr, + }, + "last error wins": { + setup: func() SimulationReporter { + r := NewBasicSimulationReporter().WithScope(myMsg) + r.Fail(errors.New("other-err"), "testing1") + r.Fail(myErr, "testing2") + return r + }, + expOp: simtypes.NewOperationMsgBasic("TestMsg", "/testpb.TestMsg", "testing1, testing2", false), + expErr: myErr, + }, + "skipped ": { + setup: func() SimulationReporter { + r := NewBasicSimulationReporter().WithScope(myMsg) + r.Skip("testing") + return r + }, + expOp: simtypes.NoOpMsg("TestMsg", "/testpb.TestMsg", "testing"), + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + r := spec.setup() + assert.Equal(t, spec.expOp, r.ToLegacyOperationMsg()) + require.Equal(t, spec.expErr, r.Close()) + }) + } +} + +func TestSimulationReporterTransitions(t *testing.T) { + specs := map[string]struct { + setup func(r SimulationReporter) + expStatus ReporterStatus + expPanic bool + }{ + "undefined->skipped": { + setup: func(r SimulationReporter) { + r.Skip("testing") + }, + expStatus: skipped, + }, + "skipped->skipped": { + setup: func(r SimulationReporter) { + r.Skip("testing1") + r.Skip("testing2") + }, + expStatus: skipped, + }, + "skipped->completed": { + setup: func(r SimulationReporter) { + r.Skip("testing1") + r.Success(nil, "testing2") + }, + expStatus: completed, + }, + "completed->completed": { + setup: func(r SimulationReporter) { + r.Success(nil, "testing1") + r.Fail(nil, "testing2") + }, + expStatus: completed, + }, + "completed->completed2": { + setup: func(r SimulationReporter) { + r.Fail(nil, "testing1") + r.Success(nil, "testing2") + }, + expStatus: completed, + }, + "completed->skipped: rejected": { + setup: func(r SimulationReporter) { + r.Success(nil, "testing1") + r.Skip("testing2") + }, + expPanic: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + r := NewBasicSimulationReporter() + if !spec.expPanic { + spec.setup(r) + assert.Equal(t, uint32(spec.expStatus), r.status.Load()) + return + } + require.Panics(t, func() { + spec.setup(r) + }) + }) + } +} + +func TestSkipHook(t *testing.T) { + myHook := func() (SkipHookFn, *bool) { + var hookCalled bool + return func(args ...any) { + hookCalled = true + }, &hookCalled + } + fn, myHookCalled := myHook() + r := NewBasicSimulationReporter(fn) + r.Skip("testing") + require.True(t, *myHookCalled) + + // and with nested reporter + fn, myHookCalled = myHook() + r = NewBasicSimulationReporter(fn) + fn2, myOtherHookCalled := myHook() + r2 := r.WithScope(testdata.NewTestMsg([]byte{1}), fn2) + r2.Skipf("testing %d", 2) + assert.True(t, *myHookCalled) + assert.True(t, *myOtherHookCalled) +} + +func TestReporterSummary(t *testing.T) { + specs := map[string]struct { + do func(t *testing.T, r SimulationReporter) + expSummary map[string]int + expReasons map[string]map[string]int + }{ + "skipped": { + do: func(t *testing.T, r SimulationReporter) { //nolint:thelper // not a helper + r2 := r.WithScope(testdata.NewTestMsg([]byte{1})) + r2.Skip("testing") + require.NoError(t, r2.Close()) + }, + expSummary: map[string]int{"TestMsg_skipped": 1}, + expReasons: map[string]map[string]int{"/testpb.TestMsg": {"testing": 1}}, + }, + "success result": { + do: func(t *testing.T, r SimulationReporter) { //nolint:thelper // not a helper + msg := testdata.NewTestMsg([]byte{1}) + r2 := r.WithScope(msg) + r2.Success(msg) + require.NoError(t, r2.Close()) + }, + expSummary: map[string]int{"TestMsg_completed": 1}, + expReasons: map[string]map[string]int{}, + }, + "error result": { + do: func(t *testing.T, r SimulationReporter) { //nolint:thelper // not a helper + msg := testdata.NewTestMsg([]byte{1}) + r2 := r.WithScope(msg) + r2.Fail(errors.New("testing")) + require.Error(t, r2.Close()) + }, + expSummary: map[string]int{"TestMsg_completed": 1}, + expReasons: map[string]map[string]int{}, + }, + "multiple skipped": { + do: func(t *testing.T, r SimulationReporter) { //nolint:thelper // not a helper + r2 := r.WithScope(testdata.NewTestMsg([]byte{1})) + r2.Skip("testing1") + require.NoError(t, r2.Close()) + r3 := r.WithScope(testdata.NewTestMsg([]byte{2})) + r3.Skip("testing2") + require.NoError(t, r3.Close()) + }, + expSummary: map[string]int{"TestMsg_skipped": 2}, + expReasons: map[string]map[string]int{ + "/testpb.TestMsg": {"testing1": 1, "testing2": 1}, + }, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + r := NewBasicSimulationReporter() + // when + spec.do(t, r) + gotSummary := r.Summary() + // then + require.Equal(t, spec.expSummary, gotSummary.counts) + require.Equal(t, spec.expReasons, gotSummary.skipReasons) + }) + } +} diff --git a/simsx/runner.go b/simsx/runner.go new file mode 100644 index 000000000000..3795cb299dae --- /dev/null +++ b/simsx/runner.go @@ -0,0 +1,377 @@ +package simsx + +import ( + "encoding/json" + "fmt" + "io" + "iter" + "os" + "path/filepath" + "testing" + + dbm "github.com/cosmos/cosmos-db" + "github.com/stretchr/testify/require" + + corestore "cosmossdk.io/core/store" + "cosmossdk.io/log" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/server" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" +) + +const SimAppChainID = "simulation-app" + +// this list of seeds was imported from the original simulation runner: https://github.com/cosmos/tools/blob/v1.0.0/cmd/runsim/main.go#L32 +var defaultSeeds = []int64{ + 1, 2, 4, 7, + 32, 123, 124, 582, 1893, 2989, + 3012, 4728, 37827, 981928, 87821, 891823782, + 989182, 89182391, 11, 22, 44, 77, 99, 2020, + 3232, 123123, 124124, 582582, 18931893, + 29892989, 30123012, 47284728, 7601778, 8090485, + 977367484, 491163361, 424254581, 673398983, +} + +// SimStateFactory is a factory type that provides a convenient way to create a simulation state for testing. +// It contains the following fields: +// - Codec: a codec used for serializing other objects +// - AppStateFn: a function that returns the app state JSON bytes and the genesis accounts +// - BlockedAddr: a map of blocked addresses +// - AccountSource: an interface for retrieving accounts +// - BalanceSource: an interface for retrieving balance-related information +type SimStateFactory struct { + Codec codec.Codec + AppStateFn simtypes.AppStateFn + BlockedAddr map[string]bool + AccountSource AccountSourceX + BalanceSource BalanceSource +} + +// SimulationApp abstract app that is used by sims +type SimulationApp interface { + runtime.AppSimI + SetNotSigverifyTx() + GetBaseApp() *baseapp.BaseApp + TxConfig() client.TxConfig + Close() error +} + +// Run is a helper function that runs a simulation test with the given parameters. +// It calls the RunWithSeeds function with the default seeds and parameters. +// +// This is the entrypoint to run simulation tests that used to run with the runsim binary. +func Run[T SimulationApp]( + t *testing.T, + appFactory func( + logger log.Logger, + db corestore.KVStoreWithBatch, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), + ) T, + setupStateFactory func(app T) SimStateFactory, + postRunActions ...func(t testing.TB, app TestInstance[T]), +) { + t.Helper() + RunWithSeeds(t, appFactory, setupStateFactory, defaultSeeds, nil, postRunActions...) +} + +// RunWithSeeds is a helper function that runs a simulation test with the given parameters. +// It iterates over the provided seeds and runs the simulation test for each seed in parallel. +// +// It sets up the environment, creates an instance of the simulation app, +// calls the simulation.SimulateFromSeed function to run the simulation, and performs post-run actions for each seed. +// The execution is deterministic and can be used for fuzz tests as well. +// +// The system under test is isolated for each run but unlike the old runsim command, there is no Process separation. +// This means, global caches may be reused for example. This implementation build upon the vanilla Go stdlib test framework. +func RunWithSeeds[T SimulationApp]( + t *testing.T, + appFactory func( + logger log.Logger, + db corestore.KVStoreWithBatch, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), + ) T, + setupStateFactory func(app T) SimStateFactory, + seeds []int64, + fuzzSeed []byte, + postRunActions ...func(t testing.TB, app TestInstance[T]), +) { + t.Helper() + cfg := cli.NewConfigFromFlags() + cfg.ChainID = SimAppChainID + for i := range seeds { + seed := seeds[i] + t.Run(fmt.Sprintf("seed: %d", seed), func(t *testing.T) { + t.Parallel() + RunWithSeed(t, cfg, appFactory, setupStateFactory, seed, fuzzSeed, postRunActions...) + }) + } +} + +// RunWithSeed is a helper function that runs a simulation test with the given parameters. +// It iterates over the provided seeds and runs the simulation test for each seed in parallel. +// +// It sets up the environment, creates an instance of the simulation app, +// calls the simulation.SimulateFromSeed function to run the simulation, and performs post-run actions for the seed. +// The execution is deterministic and can be used for fuzz tests as well. +func RunWithSeed[T SimulationApp]( + tb testing.TB, + cfg simtypes.Config, + appFactory func(logger log.Logger, db corestore.KVStoreWithBatch, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp)) T, + setupStateFactory func(app T) SimStateFactory, + seed int64, + fuzzSeed []byte, + postRunActions ...func(t testing.TB, app TestInstance[T]), +) { + tb.Helper() + // setup environment + tCfg := cfg.With(tb, seed, fuzzSeed) + testInstance := NewSimulationAppInstance(tb, tCfg, appFactory) + var runLogger log.Logger + if cli.FlagVerboseValue { + runLogger = log.NewTestLogger(tb) + } else { + runLogger = log.NewTestLoggerInfo(tb) + } + runLogger = runLogger.With("seed", tCfg.Seed) + + app := testInstance.App + stateFactory := setupStateFactory(app) + ops, reporter := prepareWeightedOps(app.SimulationManager(), stateFactory, tCfg, testInstance.App.TxConfig(), runLogger) + simParams, err := simulation.SimulateFromSeedX(tb, runLogger, WriteToDebugLog(runLogger), app.GetBaseApp(), stateFactory.AppStateFn, simtypes.RandomAccounts, ops, stateFactory.BlockedAddr, tCfg, stateFactory.Codec, testInstance.ExecLogWriter) + require.NoError(tb, err) + err = simtestutil.CheckExportSimulation(app, tCfg, simParams) + require.NoError(tb, err) + if tCfg.Commit && tCfg.DBBackend == "goleveldb" { + simtestutil.PrintStats(testInstance.DB.(*dbm.GoLevelDB), tb.Log) + } + // not using tb.Log to always print the summary + fmt.Printf("+++ DONE (seed: %d): \n%s\n", seed, reporter.Summary().String()) + for _, step := range postRunActions { + step(tb, testInstance) + } + require.NoError(tb, app.Close()) +} + +type ( + HasWeightedOperationsX interface { + WeightedOperationsX(weight WeightSource, reg Registry) + } + HasWeightedOperationsXWithProposals interface { + WeightedOperationsX(weights WeightSource, reg Registry, proposals iter.Seq2[uint32, SimMsgFactoryX], + legacyProposals []simtypes.WeightedProposalContent) //nolint: staticcheck // used for legacy proposal types + } + HasProposalMsgsX interface { + ProposalMsgsX(weights WeightSource, reg Registry) + } +) + +type ( + HasLegacyWeightedOperations interface { + // WeightedOperations simulation operations (i.e msgs) with their respective weight + WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation + } + // HasLegacyProposalMsgs defines the messages that can be used to simulate governance (v1) proposals + // Deprecated replaced by HasProposalMsgsX + HasLegacyProposalMsgs interface { + // ProposalMsgs msg fu nctions used to simulate governance proposals + ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg + } + + // HasLegacyProposalContents defines the contents that can be used to simulate legacy governance (v1beta1) proposals + // Deprecated replaced by HasProposalMsgsX + HasLegacyProposalContents interface { + // ProposalContents content functions used to simulate governance proposals + ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent //nolint:staticcheck // legacy v1beta1 governance + } +) + +// TestInstance is a generic type that represents an instance of a SimulationApp used for testing simulations. +// It contains the following fields: +// - App: The instance of the SimulationApp under test. +// - DB: The LevelDB database for the simulation app. +// - WorkDir: The temporary working directory for the simulation app. +// - Cfg: The configuration flags for the simulator. +// - AppLogger: The logger used for logging in the app during the simulation, with seed value attached. +// - ExecLogWriter: Captures block and operation data coming from the simulation +type TestInstance[T SimulationApp] struct { + App T + DB corestore.KVStoreWithBatch + WorkDir string + Cfg simtypes.Config + AppLogger log.Logger + ExecLogWriter simulation.LogWriter +} + +// included to avoid cyclic dependency in testutils/sims +func prepareWeightedOps( + sm *module.SimulationManager, + stateFact SimStateFactory, + config simtypes.Config, + txConfig client.TxConfig, + logger log.Logger, +) (simulation.WeightedOperations, *BasicSimulationReporter) { + cdc := stateFact.Codec + signingCtx := cdc.InterfaceRegistry().SigningContext() + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + AddressCodec: signingCtx.AddressCodec(), + ValidatorCodec: signingCtx.ValidatorAddressCodec(), + TxConfig: txConfig, + BondDenom: sdk.DefaultBondDenom, + } + + if config.ParamsFile != "" { + bz, err := os.ReadFile(config.ParamsFile) + if err != nil { + panic(err) + } + + err = json.Unmarshal(bz, &simState.AppParams) + if err != nil { + panic(err) + } + } + + weights := ParamWeightSource(simState.AppParams) + reporter := NewBasicSimulationReporter() + + pReg := make(UniqueTypeRegistry) + wProps := make([]simtypes.WeightedProposalMsg, 0, len(sm.Modules)) + wContent := make([]simtypes.WeightedProposalContent, 0) //nolint:staticcheck // required for legacy type + + // add gov proposals types + for _, m := range sm.Modules { + switch xm := m.(type) { + case HasProposalMsgsX: + xm.ProposalMsgsX(weights, pReg) + case HasLegacyProposalMsgs: + wProps = append(wProps, xm.ProposalMsgs(simState)...) + case HasLegacyProposalContents: + wContent = append(wContent, xm.ProposalContents(simState)...) + } + } + if len(wProps) != 0 { + panic("legacy proposals are not empty") + } + if len(wContent) != 0 { + panic("legacy proposal contents are not empty") + } + + oReg := NewSimsMsgRegistryAdapter(reporter, stateFact.AccountSource, stateFact.BalanceSource, txConfig, logger) + wOps := make([]simtypes.WeightedOperation, 0, len(sm.Modules)) + for _, m := range sm.Modules { + // add operations + switch xm := m.(type) { + case HasWeightedOperationsX: + xm.WeightedOperationsX(weights, oReg) + case HasWeightedOperationsXWithProposals: + xm.WeightedOperationsX(weights, oReg, pReg.Iterator(), nil) + case HasLegacyWeightedOperations: + wOps = append(wOps, xm.WeightedOperations(simState)...) + } + } + return append(wOps, oReg.ToLegacyObjects()...), reporter +} + +// NewSimulationAppInstance initializes and returns a TestInstance of a SimulationApp. +// The function takes a testing.T instance, a simtypes.Config instance, and an appFactory function as parameters. +// It creates a temporary working directory and a LevelDB database for the simulation app. +// The function then initializes a logger based on the verbosity flag and sets the logger's seed to the test configuration's seed. +// The database is closed and cleaned up on test completion. +func NewSimulationAppInstance[T SimulationApp]( + tb testing.TB, + tCfg simtypes.Config, + appFactory func(logger log.Logger, db corestore.KVStoreWithBatch, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp)) T, +) TestInstance[T] { + tb.Helper() + workDir := tb.TempDir() + require.NoError(tb, os.Mkdir(filepath.Join(workDir, "data"), 0o755)) + dbDir := filepath.Join(workDir, "leveldb-app-sim") + var logger log.Logger + if cli.FlagVerboseValue { + logger = log.NewTestLogger(tb) + } else { + logger = log.NewTestLoggerError(tb) + } + logger = logger.With("seed", tCfg.Seed) + db, err := dbm.NewDB("Simulation", dbm.BackendType(tCfg.DBBackend), dbDir) + require.NoError(tb, err) + tb.Cleanup(func() { + _ = db.Close() // ensure db is closed + }) + appOptions := make(simtestutil.AppOptionsMap) + appOptions[flags.FlagHome] = workDir + appOptions[server.FlagInvCheckPeriod] = cli.FlagPeriodValue + opts := []func(*baseapp.BaseApp){baseapp.SetChainID(SimAppChainID)} + if tCfg.FauxMerkle { + opts = append(opts, FauxMerkleModeOpt) + } + app := appFactory(logger, db, nil, true, appOptions, opts...) + if !cli.FlagSigverifyTxValue { + app.SetNotSigverifyTx() + } + return TestInstance[T]{ + App: app, + DB: db, + WorkDir: workDir, + Cfg: tCfg, + AppLogger: logger, + ExecLogWriter: &simulation.StandardLogWriter{Seed: tCfg.Seed}, + } +} + +var _ io.Writer = writerFn(nil) + +type writerFn func(p []byte) (n int, err error) + +func (w writerFn) Write(p []byte) (n int, err error) { + return w(p) +} + +// WriteToDebugLog is an adapter to io.Writer interface +func WriteToDebugLog(logger log.Logger) io.Writer { + return writerFn(func(p []byte) (n int, err error) { + logger.Debug(string(p)) + return len(p), nil + }) +} + +// AppOptionsFn is an adapter to the single method AppOptions interface +type AppOptionsFn func(string) any + +func (f AppOptionsFn) Get(k string) any { + return f(k) +} + +func (f AppOptionsFn) GetString(k string) string { + str, ok := f(k).(string) + if !ok { + return "" + } + + return str +} + +// FauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of +// an IAVLStore for faster simulation speed. +func FauxMerkleModeOpt(bapp *baseapp.BaseApp) { + bapp.SetFauxMerkleMode() +} diff --git a/simsx/slices.go b/simsx/slices.go new file mode 100644 index 000000000000..3466cd6f971c --- /dev/null +++ b/simsx/slices.go @@ -0,0 +1,38 @@ +package simsx + +// Collect applies the function f to each element in the source slice, +// returning a new slice containing the results. +// +// The source slice can contain elements of any type T, and the function f +// should take an element of type T as input and return a value of any type E. +// +// Example usage: +// +// source := []int{1, 2, 3, 4, 5} +// double := Collect(source, func(x int) int { +// return x * 2 +// }) +// // double is now []int{2, 4, 6, 8, 10} +func Collect[T, E any](source []T, f func(a T) E) []E { + r := make([]E, len(source)) + for i, v := range source { + r[i] = f(v) + } + return r +} + +// First returns the first element in the slice that matches the condition +func First[T any](source []T, f func(a T) bool) *T { + for i := 0; i < len(source); i++ { + if f(source[i]) { + return &source[i] + } + } + return nil +} + +// OneOf returns a random element from the given slice using the provided random number generator. +// Panics for empty or nil slice +func OneOf[T any](r interface{ Intn(n int) int }, s []T) T { + return s[r.Intn(len(s))] +} diff --git a/simsx/slices_test.go b/simsx/slices_test.go new file mode 100644 index 000000000000..40decc173bd2 --- /dev/null +++ b/simsx/slices_test.go @@ -0,0 +1,40 @@ +package simsx + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCollect(t *testing.T) { + src := []int{1, 2, 3} + got := Collect(src, func(a int) int { return a * 2 }) + assert.Equal(t, []int{2, 4, 6}, got) + gotStrings := Collect(src, strconv.Itoa) + assert.Equal(t, []string{"1", "2", "3"}, gotStrings) +} + +func TestFirst(t *testing.T) { + src := []string{"a", "b"} + assert.Equal(t, &src[1], First(src, func(a string) bool { return a == "b" })) + assert.Nil(t, First(src, func(a string) bool { return false })) +} + +func TestOneOf(t *testing.T) { + src := []string{"a", "b"} + got := OneOf(randMock{next: 1}, src) + assert.Equal(t, "b", got) + // test with other type + src2 := []int{1, 2, 3} + got2 := OneOf(randMock{next: 2}, src2) + assert.Equal(t, 3, got2) +} + +type randMock struct { + next int +} + +func (x randMock) Intn(n int) int { + return x.next +} diff --git a/tests/go.mod b/tests/go.mod index 9f834034fab6..750aae368708 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -13,7 +13,7 @@ require ( cosmossdk.io/store v1.1.1-0.20240418092142-896cdf1971bc cosmossdk.io/x/evidence v0.0.0-20230613133644-0a778132a60f cosmossdk.io/x/feegrant v0.0.0-20230613133644-0a778132a60f - cosmossdk.io/x/nft v0.0.0-20230613133644-0a778132a60f + cosmossdk.io/x/nft v0.0.0-20230613133644-0a778132a60f // indirect cosmossdk.io/x/protocolpool v0.0.0-20230925135524-a1bc045b3190 cosmossdk.io/x/tx v0.13.4 cosmossdk.io/x/upgrade v0.0.0-20230613133644-0a778132a60f diff --git a/tests/integration/bank/keeper/deterministic_test.go b/tests/integration/bank/keeper/deterministic_test.go index 575fdaead315..7233a4d0ee86 100644 --- a/tests/integration/bank/keeper/deterministic_test.go +++ b/tests/integration/bank/keeper/deterministic_test.go @@ -200,7 +200,9 @@ func TestGRPCQueryAllBalances(t *testing.T) { for i := 0; i < numCoins; i++ { coin := getCoin(rt) - + if exists, _ := coins.Find(coin.Denom); exists { + t.Skip("duplicate denom") + } // NewCoins sorts the denoms coins = sdk.NewCoins(append(coins, coin)...) } diff --git a/tests/integration/distribution/keeper/msg_server_test.go b/tests/integration/distribution/keeper/msg_server_test.go index a7be5653566a..575f031c14f7 100644 --- a/tests/integration/distribution/keeper/msg_server_test.go +++ b/tests/integration/distribution/keeper/msg_server_test.go @@ -146,8 +146,8 @@ func initFixture(t *testing.T) *fixture { authModule := auth.NewAppModule(cdc, accountKeeper, acctsModKeeper, authsims.RandomGenesisAccounts, nil) bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper) - stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper) - distrModule := distribution.NewAppModule(cdc, distrKeeper, accountKeeper, bankKeeper, stakingKeeper) + stakingModule := staking.NewAppModule(cdc, stakingKeeper) + distrModule := distribution.NewAppModule(cdc, distrKeeper, stakingKeeper) poolModule := protocolpool.NewAppModule(cdc, poolKeeper, accountKeeper, bankKeeper) consensusModule := consensus.NewAppModule(cdc, consensusParamsKeeper) diff --git a/tests/integration/evidence/keeper/infraction_test.go b/tests/integration/evidence/keeper/infraction_test.go index b666bd3fcb5b..745407688cb2 100644 --- a/tests/integration/evidence/keeper/infraction_test.go +++ b/tests/integration/evidence/keeper/infraction_test.go @@ -161,7 +161,7 @@ func initFixture(tb testing.TB) *fixture { authModule := auth.NewAppModule(cdc, accountKeeper, acctsModKeeper, authsims.RandomGenesisAccounts, nil) bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper) - stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper) + stakingModule := staking.NewAppModule(cdc, stakingKeeper) slashingModule := slashing.NewAppModule(cdc, slashingKeeper, accountKeeper, bankKeeper, stakingKeeper, cdc.InterfaceRegistry(), cometInfoService) evidenceModule := evidence.NewAppModule(cdc, *evidenceKeeper, cometInfoService) consensusModule := consensus.NewAppModule(cdc, consensusParamsKeeper) diff --git a/tests/integration/genutil/init_test.go b/tests/integration/genutil/init_test.go index 9116db0ce8fe..12fd1ccd0292 100644 --- a/tests/integration/genutil/init_test.go +++ b/tests/integration/genutil/init_test.go @@ -37,7 +37,7 @@ import ( ) var testMbm = module.NewManager( - staking.NewAppModule(makeCodec(), nil, nil, nil), + staking.NewAppModule(makeCodec(), nil), genutil.NewAppModule(makeCodec(), nil, nil, nil, nil, nil), ) diff --git a/tests/integration/gov/keeper/keeper_test.go b/tests/integration/gov/keeper/keeper_test.go index da20fc9b2b98..f1f94f575d58 100644 --- a/tests/integration/gov/keeper/keeper_test.go +++ b/tests/integration/gov/keeper/keeper_test.go @@ -149,7 +149,7 @@ func initFixture(tb testing.TB) *fixture { authModule := auth.NewAppModule(cdc, accountKeeper, acctsModKeeper, authsims.RandomGenesisAccounts, nil) bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper) - stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper) + stakingModule := staking.NewAppModule(cdc, stakingKeeper) govModule := gov.NewAppModule(cdc, govKeeper, accountKeeper, bankKeeper, poolKeeper) consensusModule := consensus.NewAppModule(cdc, consensusParamsKeeper) diff --git a/tests/integration/slashing/keeper/keeper_test.go b/tests/integration/slashing/keeper/keeper_test.go index a50dcac4fa05..e961dc68233d 100644 --- a/tests/integration/slashing/keeper/keeper_test.go +++ b/tests/integration/slashing/keeper/keeper_test.go @@ -126,7 +126,7 @@ func initFixture(tb testing.TB) *fixture { slashingKeeper := slashingkeeper.NewKeeper(runtime.NewEnvironment(runtime.NewKVStoreService(keys[slashingtypes.StoreKey]), log.NewNopLogger(), runtime.EnvWithQueryRouterService(queryRouter), runtime.EnvWithMsgRouterService(msgRouter)), cdc, &codec.LegacyAmino{}, stakingKeeper, authority.String()) bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper) - stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper) + stakingModule := staking.NewAppModule(cdc, stakingKeeper) slashingModule := slashing.NewAppModule(cdc, slashingKeeper, accountKeeper, bankKeeper, stakingKeeper, cdc.InterfaceRegistry(), cometInfoService) consensusModule := consensus.NewAppModule(cdc, consensusParamsKeeper) diff --git a/tests/integration/staking/keeper/common_test.go b/tests/integration/staking/keeper/common_test.go index 26d0e217afaa..e3963efb7bb7 100644 --- a/tests/integration/staking/keeper/common_test.go +++ b/tests/integration/staking/keeper/common_test.go @@ -170,7 +170,7 @@ func initFixture(tb testing.TB) *fixture { authModule := auth.NewAppModule(cdc, accountKeeper, acctsModKeeper, authsims.RandomGenesisAccounts, nil) bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper) - stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper) + stakingModule := staking.NewAppModule(cdc, stakingKeeper) consensusModule := consensus.NewAppModule(cdc, consensusParamsKeeper) integrationApp := integration.NewIntegrationApp(newCtx, logger, keys, cdc, diff --git a/tests/integration/staking/keeper/deterministic_test.go b/tests/integration/staking/keeper/deterministic_test.go index ca5f1047808d..4a15c7775a35 100644 --- a/tests/integration/staking/keeper/deterministic_test.go +++ b/tests/integration/staking/keeper/deterministic_test.go @@ -133,7 +133,7 @@ func initDeterministicFixture(t *testing.T) *deterministicFixture { authModule := auth.NewAppModule(cdc, accountKeeper, acctsModKeeper, authsims.RandomGenesisAccounts, nil) bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper) - stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper) + stakingModule := staking.NewAppModule(cdc, stakingKeeper) consensusModule := consensus.NewAppModule(cdc, consensusParamsKeeper) integrationApp := integration.NewIntegrationApp(newCtx, logger, keys, cdc, diff --git a/tests/integration/staking/simulation/operations_test.go b/tests/integration/staking/simulation/operations_test.go deleted file mode 100644 index a5a6fbc41e14..000000000000 --- a/tests/integration/staking/simulation/operations_test.go +++ /dev/null @@ -1,443 +0,0 @@ -package simulation_test - -import ( - "math/big" - "math/rand" - "testing" - "time" - - abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" - cmttypes "github.com/cometbft/cometbft/types" - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - - "cosmossdk.io/collections" - "cosmossdk.io/core/header" - "cosmossdk.io/depinject" - sdklog "cosmossdk.io/log" - "cosmossdk.io/math" - bankkeeper "cosmossdk.io/x/bank/keeper" - banktestutil "cosmossdk.io/x/bank/testutil" - distrkeeper "cosmossdk.io/x/distribution/keeper" - distrtypes "cosmossdk.io/x/distribution/types" - mintkeeper "cosmossdk.io/x/mint/keeper" - minttypes "cosmossdk.io/x/mint/types" - stakingkeeper "cosmossdk.io/x/staking/keeper" - "cosmossdk.io/x/staking/simulation" - "cosmossdk.io/x/staking/testutil" - "cosmossdk.io/x/staking/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec/address" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - "github.com/cosmos/cosmos-sdk/runtime" - "github.com/cosmos/cosmos-sdk/tests/integration/staking" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" -) - -type SimTestSuite struct { - suite.Suite - - r *rand.Rand - txConfig client.TxConfig - accounts []simtypes.Account - ctx sdk.Context - app *runtime.App - bankKeeper bankkeeper.Keeper - accountKeeper authkeeper.AccountKeeper - distrKeeper distrkeeper.Keeper - stakingKeeper *stakingkeeper.Keeper - - encCfg moduletestutil.TestEncodingConfig -} - -func (s *SimTestSuite) SetupTest() { - sdk.DefaultPowerReduction = math.NewIntFromBigInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) - - s.r = rand.New(rand.NewSource(1)) - accounts := simtypes.RandomAccounts(s.r, 4) - - // create genesis accounts - senderPrivKey := secp256k1.GenPrivKey() - acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) - accs := []simtestutil.GenesisAccount{ - {GenesisAccount: acc, Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(100000000000000)))}, - } - - // create validator set with single validator - account := accounts[0] - cmtPk, err := cryptocodec.ToCmtPubKeyInterface(account.ConsKey.PubKey()) - require.NoError(s.T(), err) - validator := cmttypes.NewValidator(cmtPk, 1) - - startupCfg := simtestutil.DefaultStartUpConfig() - startupCfg.GenesisAccounts = accs - startupCfg.ValidatorSet = func() (*cmttypes.ValidatorSet, error) { - return cmttypes.NewValidatorSet([]*cmttypes.Validator{validator}), nil - } - - var ( - accountKeeper authkeeper.AccountKeeper - mintKeeper mintkeeper.Keeper - bankKeeper bankkeeper.Keeper - distrKeeper distrkeeper.Keeper - stakingKeeper *stakingkeeper.Keeper - ) - - cfg := depinject.Configs( - staking.AppConfig, - depinject.Supply(sdklog.NewNopLogger()), - ) - - app, err := simtestutil.SetupWithConfiguration(cfg, startupCfg, &s.txConfig, &bankKeeper, &accountKeeper, &mintKeeper, &distrKeeper, &stakingKeeper) - require.NoError(s.T(), err) - - ctx := app.BaseApp.NewContext(false) - s.Require().NoError(mintKeeper.Params.Set(ctx, minttypes.DefaultParams())) - s.Require().NoError(mintKeeper.Minter.Set(ctx, minttypes.DefaultInitialMinter())) - - initAmt := stakingKeeper.TokensFromConsensusPower(ctx, 200) - initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) - - s.accounts = accounts - // remove genesis validator account - // add coins to the accounts - for _, account := range accounts[1:] { - acc := accountKeeper.NewAccountWithAddress(ctx, account.Address) - accountKeeper.SetAccount(ctx, acc) - s.Require().NoError(banktestutil.FundAccount(ctx, bankKeeper, account.Address, initCoins)) - } - - s.accountKeeper = accountKeeper - s.bankKeeper = bankKeeper - s.distrKeeper = distrKeeper - s.stakingKeeper = stakingKeeper - s.ctx = ctx - s.app = app -} - -// TestWeightedOperations tests the weights of the operations. -func (s *SimTestSuite) TestWeightedOperations() { - require := s.Require() - - s.ctx.WithChainID("test-chain") - - cdc := s.encCfg.Codec - appParams := make(simtypes.AppParams) - - weightedOps := simulation.WeightedOperations(appParams, cdc, s.txConfig, s.accountKeeper, - s.bankKeeper, s.stakingKeeper, - ) - - expected := []struct { - weight int - opMsgRoute string - opMsgName string - }{ - {simulation.DefaultWeightMsgCreateValidator, types.ModuleName, sdk.MsgTypeURL(&types.MsgCreateValidator{})}, - {simulation.DefaultWeightMsgEditValidator, types.ModuleName, sdk.MsgTypeURL(&types.MsgEditValidator{})}, - {simulation.DefaultWeightMsgDelegate, types.ModuleName, sdk.MsgTypeURL(&types.MsgDelegate{})}, - {simulation.DefaultWeightMsgUndelegate, types.ModuleName, sdk.MsgTypeURL(&types.MsgUndelegate{})}, - {simulation.DefaultWeightMsgBeginRedelegate, types.ModuleName, sdk.MsgTypeURL(&types.MsgBeginRedelegate{})}, - {simulation.DefaultWeightMsgCancelUnbondingDelegation, types.ModuleName, sdk.MsgTypeURL(&types.MsgCancelUnbondingDelegation{})}, - {simulation.DefaultWeightMsgRotateConsPubKey, types.ModuleName, sdk.MsgTypeURL(&types.MsgRotateConsPubKey{})}, - } - - for i, w := range weightedOps { - operationMsg, _, _ := w.Op()(s.r, s.app.BaseApp, s.ctx, s.accounts, s.ctx.ChainID()) - // require.NoError(t, err) // TODO check if it should be NoError - - // the following checks are very much dependent from the ordering of the output given - // by WeightedOperations. if the ordering in WeightedOperations changes some tests - // will fail - require.Equal(expected[i].weight, w.Weight(), "weight should be the same") - require.Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") - require.Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") - } -} - -// TestSimulateMsgCreateValidator tests the normal scenario of a valid message of type TypeMsgCreateValidator. -// Abonormal scenarios, where the message are created by an errors are not tested here. -func (s *SimTestSuite) TestSimulateMsgCreateValidator() { - require := s.Require() - _, err := s.app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: s.app.LastBlockHeight() + 1, Hash: s.app.LastCommitID().Hash}) - require.NoError(err) - // execute operation - op := simulation.SimulateMsgCreateValidator(s.txConfig, s.accountKeeper, s.bankKeeper, s.stakingKeeper) - operationMsg, futureOperations, err := op(s.r, s.app.BaseApp, s.ctx, s.accounts[1:], "") - require.NoError(err) - - var msg types.MsgCreateValidator - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(err) - require.True(operationMsg.OK) - require.Equal(sdk.MsgTypeURL(&types.MsgCreateValidator{}), sdk.MsgTypeURL(&msg)) - valaddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) - require.NoError(err) - require.Equal("cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", sdk.AccAddress(valaddr).String()) - require.Equal("cosmosvaloper1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7epjs3u", msg.ValidatorAddress) - require.Len(futureOperations, 0) -} - -// TestSimulateMsgCancelUnbondingDelegation tests the normal scenario of a valid message of type TypeMsgCancelUnbondingDelegation. -// Abonormal scenarios, where the message is -func (s *SimTestSuite) TestSimulateMsgCancelUnbondingDelegation() { - require := s.Require() - blockTime := time.Now().UTC() - ctx := s.ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - // setup accounts[1] as validator - validator0 := s.getTestingValidator0(ctx) - - // setup delegation - delTokens := s.stakingKeeper.TokensFromConsensusPower(ctx, 2) - validator0, issuedShares := validator0.AddTokensFromDel(delTokens) - delegator := s.accounts[2] - delegation := types.NewDelegation(delegator.Address.String(), validator0.GetOperator(), issuedShares) - require.NoError(s.stakingKeeper.SetDelegation(ctx, delegation)) - val0bz, err := s.stakingKeeper.ValidatorAddressCodec().StringToBytes(validator0.GetOperator()) - s.Require().NoError(err) - s.Require().NoError(s.distrKeeper.DelegatorStartingInfo.Set(ctx, collections.Join(sdk.ValAddress(val0bz), delegator.Address), distrtypes.NewDelegatorStartingInfo(2, math.LegacyOneDec(), 200))) - - s.setupValidatorRewards(ctx, val0bz) - - // unbonding delegation - udb := types.NewUnbondingDelegation(delegator.Address, val0bz, s.app.LastBlockHeight()+1, blockTime.Add(2*time.Minute), delTokens, 0, address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos")) - require.NoError(s.stakingKeeper.SetUnbondingDelegation(ctx, udb)) - s.setupValidatorRewards(ctx, val0bz) - - _, err = s.app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: s.app.LastBlockHeight() + 1, Hash: s.app.LastCommitID().Hash, Time: blockTime}) - require.NoError(err) - // execute operation - op := simulation.SimulateMsgCancelUnbondingDelegate(s.txConfig, s.accountKeeper, s.bankKeeper, s.stakingKeeper) - accounts := []simtypes.Account{delegator} - operationMsg, futureOperations, err := op(s.r, s.app.BaseApp, ctx, accounts, "") - require.NoError(err) - - var msg types.MsgCancelUnbondingDelegation - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(err) - require.True(operationMsg.OK) - require.Equal(sdk.MsgTypeURL(&types.MsgCancelUnbondingDelegation{}), sdk.MsgTypeURL(&msg)) - require.Equal(delegator.Address.String(), msg.DelegatorAddress) - require.Equal(validator0.GetOperator(), msg.ValidatorAddress) - require.Len(futureOperations, 0) -} - -// TestSimulateMsgEditValidator tests the normal scenario of a valid message of type TypeMsgEditValidator. -// Abonormal scenarios, where the message is created by an errors are not tested here. -func (s *SimTestSuite) TestSimulateMsgEditValidator() { - require := s.Require() - blockTime := time.Now().UTC() - ctx := s.ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - // setup accounts[0] as validator - _ = s.getTestingValidator0(ctx) - - _, err := s.app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: s.app.LastBlockHeight() + 1, Hash: s.app.LastCommitID().Hash, Time: blockTime}) - require.NoError(err) - // execute operation - op := simulation.SimulateMsgEditValidator(s.txConfig, s.accountKeeper, s.bankKeeper, s.stakingKeeper) - operationMsg, futureOperations, err := op(s.r, s.app.BaseApp, ctx, s.accounts, "") - require.NoError(err) - - var msg types.MsgEditValidator - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(err) - require.True(operationMsg.OK) - require.Equal(sdk.MsgTypeURL(&types.MsgEditValidator{}), sdk.MsgTypeURL(&msg)) - require.Equal("cosmosvaloper1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7epjs3u", msg.ValidatorAddress) - require.Len(futureOperations, 0) -} - -// TestSimulateMsgDelegate tests the normal scenario of a valid message of type TypeMsgDelegate. -// Abonormal scenarios, where the message is created by an errors are not tested here. -func (s *SimTestSuite) TestSimulateMsgDelegate() { - require := s.Require() - blockTime := time.Now().UTC() - ctx := s.ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - // execute operation - op := simulation.SimulateMsgDelegate(s.txConfig, s.accountKeeper, s.bankKeeper, s.stakingKeeper) - operationMsg, futureOperations, err := op(s.r, s.app.BaseApp, ctx, s.accounts[1:], "") - require.NoError(err) - - var msg types.MsgDelegate - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(err) - require.True(operationMsg.OK) - require.Equal("cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", msg.DelegatorAddress) - require.Equal("stake", msg.Amount.Denom) - require.Equal(sdk.MsgTypeURL(&types.MsgDelegate{}), sdk.MsgTypeURL(&msg)) - require.Equal("cosmosvaloper122js6qry7nlgp63gcse8muknspuxur77vj3kkr", msg.ValidatorAddress) - require.Len(futureOperations, 0) -} - -// TestSimulateMsgUndelegate tests the normal scenario of a valid message of type TypeMsgUndelegate. -// Abonormal scenarios, where the message is created by an errors are not tested here. -func (s *SimTestSuite) TestSimulateMsgUndelegate() { - require := s.Require() - blockTime := time.Now().UTC() - ctx := s.ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - // setup accounts[1] as validator - validator0 := s.getTestingValidator0(ctx) - - // setup delegation - delTokens := s.stakingKeeper.TokensFromConsensusPower(ctx, 2) - validator0, issuedShares := validator0.AddTokensFromDel(delTokens) - delegator := s.accounts[2] - delegation := types.NewDelegation(delegator.Address.String(), validator0.GetOperator(), issuedShares) - require.NoError(s.stakingKeeper.SetDelegation(ctx, delegation)) - val0bz, err := s.stakingKeeper.ValidatorAddressCodec().StringToBytes(validator0.GetOperator()) - s.Require().NoError(err) - s.Require().NoError(s.distrKeeper.DelegatorStartingInfo.Set(ctx, collections.Join(sdk.ValAddress(val0bz), delegator.Address), distrtypes.NewDelegatorStartingInfo(2, math.LegacyOneDec(), 200))) - - s.setupValidatorRewards(ctx, val0bz) - - _, err = s.app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: s.app.LastBlockHeight() + 1, Hash: s.app.LastCommitID().Hash, Time: blockTime}) - require.NoError(err) - // execute operation - op := simulation.SimulateMsgUndelegate(s.txConfig, s.accountKeeper, s.bankKeeper, s.stakingKeeper) - operationMsg, futureOperations, err := op(s.r, s.app.BaseApp, ctx, s.accounts, "") - require.NoError(err) - - var msg types.MsgUndelegate - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(err) - require.True(operationMsg.OK) - require.Equal("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.DelegatorAddress) - require.Equal("1646627814093010272", msg.Amount.Amount.String()) - require.Equal("stake", msg.Amount.Denom) - require.Equal(sdk.MsgTypeURL(&types.MsgUndelegate{}), sdk.MsgTypeURL(&msg)) - require.Equal("cosmosvaloper1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7epjs3u", msg.ValidatorAddress) - require.Len(futureOperations, 0) -} - -// TestSimulateMsgBeginRedelegate tests the normal scenario of a valid message of type TypeMsgBeginRedelegate. -// Abonormal scenarios, where the message is created by an errors, are not tested here. -func (s *SimTestSuite) TestSimulateMsgBeginRedelegate() { - require := s.Require() - blockTime := time.Now().UTC() - ctx := s.ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - // setup accounts[1] as validator0 and accounts[2] as validator1 - validator0 := s.getTestingValidator0(ctx) - validator1 := s.getTestingValidator1(ctx) - - delTokens := s.stakingKeeper.TokensFromConsensusPower(ctx, 2) - validator1, issuedShares := validator1.AddTokensFromDel(delTokens) - - // setup accounts[3] as delegator - delegator := s.accounts[3] - delegation := types.NewDelegation(delegator.Address.String(), validator0.GetOperator(), issuedShares) - require.NoError(s.stakingKeeper.SetDelegation(ctx, delegation)) - val0bz, err := s.stakingKeeper.ValidatorAddressCodec().StringToBytes(validator0.GetOperator()) - s.Require().NoError(err) - val1bz, err := s.stakingKeeper.ValidatorAddressCodec().StringToBytes(validator1.GetOperator()) - s.Require().NoError(err) - s.Require().NoError(s.distrKeeper.DelegatorStartingInfo.Set(ctx, collections.Join(sdk.ValAddress(val0bz), delegator.Address), distrtypes.NewDelegatorStartingInfo(2, math.LegacyOneDec(), 200))) - - s.setupValidatorRewards(ctx, val0bz) - s.setupValidatorRewards(ctx, val1bz) - - _, err = s.app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: s.app.LastBlockHeight() + 1, Hash: s.app.LastCommitID().Hash, Time: blockTime}) - require.NoError(err) - - // execute operation - op := simulation.SimulateMsgBeginRedelegate(s.txConfig, s.accountKeeper, s.bankKeeper, s.stakingKeeper) - operationMsg, futureOperations, err := op(s.r, s.app.BaseApp, ctx, s.accounts, "") - s.T().Logf("operation message: %v", operationMsg) - require.NoError(err) - - var msg types.MsgBeginRedelegate - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(err) - require.True(operationMsg.OK) - require.Equal("cosmos1ua0fwyws7vzjrry3pqkklvf8mny93l9s9zg0h4", msg.DelegatorAddress) - require.Equal("stake", msg.Amount.Denom) - require.Equal(sdk.MsgTypeURL(&types.MsgBeginRedelegate{}), sdk.MsgTypeURL(&msg)) - require.Equal("cosmosvaloper1ghekyjucln7y67ntx7cf27m9dpuxxemnsvnaes", msg.ValidatorDstAddress) - require.Equal("cosmosvaloper1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7epjs3u", msg.ValidatorSrcAddress) - require.Len(futureOperations, 0) -} - -func (s *SimTestSuite) TestSimulateRotateConsPubKey() { - require := s.Require() - blockTime := time.Now().UTC() - ctx := s.ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - _ = s.getTestingValidator2(ctx) - - // begin a new block - _, err := s.app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: s.app.LastBlockHeight() + 1, Hash: s.app.LastCommitID().Hash, Time: blockTime}) - require.NoError(err) - - // execute operation - op := simulation.SimulateMsgRotateConsPubKey(s.txConfig, s.accountKeeper, s.bankKeeper, s.stakingKeeper) - operationMsg, futureOperations, err := op(s.r, s.app.BaseApp, ctx, s.accounts, "") - require.NoError(err) - - var msg types.MsgRotateConsPubKey - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(err) - - require.True(operationMsg.OK) - require.Equal(sdk.MsgTypeURL(&types.MsgRotateConsPubKey{}), sdk.MsgTypeURL(&msg)) - require.Equal("cosmosvaloper1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7epjs3u", msg.ValidatorAddress) - require.Len(futureOperations, 0) -} - -func (s *SimTestSuite) getTestingValidator0(ctx sdk.Context) types.Validator { - commission0 := types.NewCommission(math.LegacyZeroDec(), math.LegacyOneDec(), math.LegacyOneDec()) - return s.getTestingValidator(ctx, commission0, 1) -} - -func (s *SimTestSuite) getTestingValidator1(ctx sdk.Context) types.Validator { - commission1 := types.NewCommission(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec()) - return s.getTestingValidator(ctx, commission1, 2) -} - -func (s *SimTestSuite) getTestingValidator(ctx sdk.Context, commission types.Commission, n int) types.Validator { - account := s.accounts[n] - valPubKey := account.PubKey - valAddr := sdk.ValAddress(account.PubKey.Address().Bytes()) - validator := testutil.NewValidator(s.T(), valAddr, valPubKey) - validator, err := validator.SetInitialCommission(commission) - s.Require().NoError(err) - - validator.DelegatorShares = math.LegacyNewDec(100) - validator.Tokens = s.stakingKeeper.TokensFromConsensusPower(ctx, 100) - - s.Require().NoError(s.stakingKeeper.SetValidator(ctx, validator)) - - return validator -} - -func (s *SimTestSuite) getTestingValidator2(ctx sdk.Context) types.Validator { - val := s.getTestingValidator0(ctx) - val.Status = types.Bonded - s.Require().NoError(s.stakingKeeper.SetValidator(ctx, val)) - s.Require().NoError(s.stakingKeeper.SetValidatorByConsAddr(ctx, val)) - return val -} - -func (s *SimTestSuite) setupValidatorRewards(ctx sdk.Context, valAddress sdk.ValAddress) { - decCoins := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, math.LegacyOneDec())} - historicalRewards := distrtypes.NewValidatorHistoricalRewards(decCoins, 2) - s.Require().NoError(s.distrKeeper.ValidatorHistoricalRewards.Set(ctx, collections.Join(valAddress, uint64(2)), historicalRewards)) - // setup current revards - currentRewards := distrtypes.NewValidatorCurrentRewards(decCoins, 3) - s.Require().NoError(s.distrKeeper.ValidatorCurrentRewards.Set(ctx, valAddress, currentRewards)) -} - -func TestSimTestSuite(t *testing.T) { - suite.Run(t, new(SimTestSuite)) -} diff --git a/tests/sims/authz/operations_test.go b/tests/sims/authz/operations_test.go deleted file mode 100644 index 3769d30c798b..000000000000 --- a/tests/sims/authz/operations_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package authz - -import ( - "math/rand" - "testing" - "time" - - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/suite" - - "cosmossdk.io/core/header" - coretesting "cosmossdk.io/core/testing" - "cosmossdk.io/depinject" - _ "cosmossdk.io/x/accounts" // import as blank for app wiring - "cosmossdk.io/x/authz" - authzkeeper "cosmossdk.io/x/authz/keeper" - _ "cosmossdk.io/x/authz/module" // import as blank for app wiring - "cosmossdk.io/x/authz/simulation" - _ "cosmossdk.io/x/bank" // import as blank for app wiring - bankkeeper "cosmossdk.io/x/bank/keeper" - banktestutil "cosmossdk.io/x/bank/testutil" - banktypes "cosmossdk.io/x/bank/types" - _ "cosmossdk.io/x/consensus" // import as blank for app wiring - _ "cosmossdk.io/x/gov" // import as blank for app wiring - _ "cosmossdk.io/x/mint" // import as blank for app wiring - _ "cosmossdk.io/x/staking" // import as blank for app wiring - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/runtime" - "github.com/cosmos/cosmos-sdk/testutil/configurator" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - _ "github.com/cosmos/cosmos-sdk/x/auth" // import as blank for app wiring - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" // import as blank for app wiring - _ "github.com/cosmos/cosmos-sdk/x/genutil" // import as blank for app wiring -) - -var AppConfig = configurator.NewAppConfig( - configurator.AccountsModule(), - configurator.AuthModule(), - configurator.BankModule(), - configurator.StakingModule(), - configurator.TxModule(), - configurator.ConsensusModule(), - configurator.GenutilModule(), - configurator.AuthzModule(), - configurator.MintModule(), -) - -type SimTestSuite struct { - suite.Suite - - ctx sdk.Context - - app *runtime.App - codec codec.Codec - interfaceRegistry codectypes.InterfaceRegistry - txConfig client.TxConfig - accountKeeper authkeeper.AccountKeeper - bankKeeper bankkeeper.Keeper - authzKeeper authzkeeper.Keeper -} - -func (suite *SimTestSuite) SetupTest() { - app, err := simtestutil.Setup( - depinject.Configs( - AppConfig, - depinject.Supply(coretesting.NewNopLogger()), - ), - &suite.codec, - &suite.interfaceRegistry, - &suite.txConfig, - &suite.accountKeeper, - &suite.bankKeeper, - &suite.authzKeeper, - ) - suite.Require().NoError(err) - suite.app = app - suite.ctx = app.BaseApp.NewContext(false) -} - -func (suite *SimTestSuite) TestWeightedOperations() { - cdc := suite.codec - appParams := make(simtypes.AppParams) - - weightedOps := simulation.WeightedOperations(suite.interfaceRegistry, appParams, cdc, suite.txConfig, suite.accountKeeper, - suite.bankKeeper, suite.authzKeeper) - - s := rand.NewSource(3) - r := rand.New(s) - // setup 2 accounts - accs := suite.getTestingAccounts(r, 2) - - expected := []struct { - weight int - opMsgRoute string - opMsgName string - }{ - {simulation.WeightGrant, authz.ModuleName, simulation.TypeMsgGrant}, - {simulation.WeightExec, authz.ModuleName, simulation.TypeMsgExec}, - {simulation.WeightRevoke, authz.ModuleName, simulation.TypeMsgRevoke}, - } - - require := suite.Require() - for i, w := range weightedOps { - op, _, err := w.Op()(r, suite.app.BaseApp, suite.ctx, accs, "") - require.NoError(err) - - // the following checks are very much dependent from the ordering of the output given - // by WeightedOperations. if the ordering in WeightedOperations changes some tests - // will fail - require.Equal(expected[i].weight, w.Weight(), "weight should be the same. %v", op.Comment) - require.Equal(expected[i].opMsgRoute, op.Route, "route should be the same. %v", op.Comment) - require.Equal(expected[i].opMsgName, op.Name, "operation Msg name should be the same %v", op.Comment) - } -} - -func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { - accounts := simtypes.RandomAccounts(r, n) - - initAmt := sdk.TokensFromConsensusPower(200000, sdk.DefaultPowerReduction) - initCoins := sdk.NewCoins(sdk.NewCoin("stake", initAmt)) - - // add coins to the accounts - for _, account := range accounts { - acc := suite.accountKeeper.NewAccountWithAddress(suite.ctx, account.Address) - suite.accountKeeper.SetAccount(suite.ctx, acc) - suite.Require().NoError(banktestutil.FundAccount(suite.ctx, suite.bankKeeper, account.Address, initCoins)) - } - - return accounts -} - -func (suite *SimTestSuite) TestSimulateGrant() { - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 2) - blockTime := time.Now().UTC() - ctx := suite.ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - granter := accounts[0] - grantee := accounts[1] - - // execute operation - op := simulation.SimulateMsgGrant(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.authzKeeper) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, ctx, accounts, "") - suite.Require().NoError(err) - - var msg authz.MsgGrant - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(granter.Address.String(), msg.Granter) - suite.Require().Equal(grantee.Address.String(), msg.Grantee) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateRevoke() { - // setup 3 accounts - s := rand.NewSource(2) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 3) - initAmt := sdk.TokensFromConsensusPower(200000, sdk.DefaultPowerReduction) - initCoins := sdk.NewCoins(sdk.NewCoin("stake", initAmt)) - - granter := accounts[0] - grantee := accounts[1] - a := banktypes.NewSendAuthorization(initCoins, nil, suite.accountKeeper.AddressCodec()) - expire := time.Now().Add(30 * time.Hour) - - err := suite.authzKeeper.SaveGrant(suite.ctx, grantee.Address, granter.Address, a, &expire) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgRevoke(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.authzKeeper) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg authz.MsgRevoke - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(granter.Address.String(), msg.Granter) - suite.Require().Equal(grantee.Address.String(), msg.Grantee) - suite.Require().Equal(banktypes.SendAuthorization{}.MsgTypeURL(), msg.MsgTypeUrl) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateExec() { - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 3) - initAmt := sdk.TokensFromConsensusPower(200000, sdk.DefaultPowerReduction) - initCoins := sdk.NewCoins(sdk.NewCoin("stake", initAmt)) - - granter := accounts[0] - grantee := accounts[1] - a := banktypes.NewSendAuthorization(initCoins, nil, suite.accountKeeper.AddressCodec()) - expire := suite.ctx.HeaderInfo().Time.Add(1 * time.Hour) - - err := suite.authzKeeper.SaveGrant(suite.ctx, grantee.Address, granter.Address, a, &expire) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgExec(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.authzKeeper, suite.codec) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg authz.MsgExec - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(grantee.Address.String(), msg.Grantee) - suite.Require().Len(futureOperations, 0) -} - -func TestSimTestSuite(t *testing.T) { - suite.Run(t, new(SimTestSuite)) -} diff --git a/tests/sims/bank/operations_test.go b/tests/sims/bank/operations_test.go deleted file mode 100644 index e2177fb14a20..000000000000 --- a/tests/sims/bank/operations_test.go +++ /dev/null @@ -1,217 +0,0 @@ -package simulation_test - -import ( - "math/rand" - "testing" - - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/suite" - - "cosmossdk.io/depinject" - "cosmossdk.io/log" - _ "cosmossdk.io/x/accounts" - _ "cosmossdk.io/x/bank" - "cosmossdk.io/x/bank/keeper" - "cosmossdk.io/x/bank/simulation" - "cosmossdk.io/x/bank/testutil" - "cosmossdk.io/x/bank/types" - _ "cosmossdk.io/x/consensus" - _ "cosmossdk.io/x/staking" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/runtime" - "github.com/cosmos/cosmos-sdk/testutil/configurator" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - _ "github.com/cosmos/cosmos-sdk/x/auth" - _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" -) - -type SimTestSuite struct { - suite.Suite - - ctx sdk.Context - accountKeeper types.AccountKeeper - bankKeeper keeper.Keeper - cdc codec.Codec - txConfig client.TxConfig - app *runtime.App -} - -func (suite *SimTestSuite) SetupTest() { - var ( - appBuilder *runtime.AppBuilder - err error - ) - suite.app, err = simtestutil.Setup( - depinject.Configs( - configurator.NewAppConfig( - configurator.AccountsModule(), - configurator.AuthModule(), - configurator.BankModule(), - configurator.StakingModule(), - configurator.ConsensusModule(), - configurator.TxModule(), - ), - depinject.Supply(log.NewNopLogger()), - ), &suite.accountKeeper, &suite.bankKeeper, &suite.cdc, &suite.txConfig, &appBuilder) - - suite.NoError(err) - - suite.ctx = suite.app.BaseApp.NewContext(false) -} - -// TestWeightedOperations tests the weights of the operations. -func (suite *SimTestSuite) TestWeightedOperations() { - cdc := suite.cdc - appParams := make(simtypes.AppParams) - - weightesOps := simulation.WeightedOperations(appParams, cdc, suite.txConfig, suite.accountKeeper, suite.bankKeeper) - - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accs := suite.getTestingAccounts(r, 3) - - expected := []struct { - weight int - opMsgRoute string - opMsgName string - }{ - {100, types.ModuleName, sdk.MsgTypeURL(&types.MsgSend{})}, - {10, types.ModuleName, sdk.MsgTypeURL(&types.MsgMultiSend{})}, - } - - for i, w := range weightesOps { - operationMsg, _, err := w.Op()(r, suite.app.BaseApp, suite.ctx, accs, "") - suite.Require().NoError(err) - - // the following checks are very much dependent from the ordering of the output given - // by WeightedOperations. if the ordering in WeightedOperations changes some tests - // will fail - suite.Require().Equal(expected[i].weight, w.Weight(), "weight should be the same") - suite.Require().Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") - suite.Require().Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") - } -} - -// TestSimulateMsgSend tests the normal scenario of a valid message of type TypeMsgSend. -// Abonormal scenarios, where the message is created by an errors, are not tested here. -func (suite *SimTestSuite) TestSimulateMsgSend() { - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 3) - - // execute operation - op := simulation.SimulateMsgSend(suite.txConfig, suite.accountKeeper, suite.bankKeeper) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg types.MsgSend - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal("65337742stake", msg.Amount.String()) - suite.Require().Equal("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.FromAddress) - suite.Require().Equal("cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", msg.ToAddress) - suite.Require().Equal(sdk.MsgTypeURL(&types.MsgSend{}), sdk.MsgTypeURL(&msg)) - suite.Require().Len(futureOperations, 0) -} - -// TestSimulateMsgMultiSend tests the normal scenario of a valid message of type TypeMsgMultiSend. -// Abonormal scenarios, where the message is created by an errors, are not tested here. -func (suite *SimTestSuite) TestSimulateMsgMultiSend() { - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 3) - - // execute operation - op := simulation.SimulateMsgMultiSend(suite.txConfig, suite.accountKeeper, suite.bankKeeper) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - require := suite.Require() - require.NoError(err) - - var msg types.MsgMultiSend - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - require.True(operationMsg.OK) - require.Len(msg.Inputs, 1) - require.Equal("cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Inputs[0].Address) - require.Equal("114949958stake", msg.Inputs[0].Coins.String()) - require.Len(msg.Outputs, 2) - require.Equal("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Outputs[1].Address) - require.Equal("107287087stake", msg.Outputs[1].Coins.String()) - suite.Require().Equal(sdk.MsgTypeURL(&types.MsgMultiSend{}), sdk.MsgTypeURL(&msg)) - require.Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateModuleAccountMsgSend() { - const moduleAccount = 1 - - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 1) - - // execute operation - op := simulation.SimulateMsgSendToModuleAccount(suite.txConfig, suite.accountKeeper, suite.bankKeeper, moduleAccount) - - s = rand.NewSource(1) - r = rand.New(s) - - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().Error(err) - - var msg types.MsgSend - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().False(operationMsg.OK) - suite.Require().Equal(operationMsg.Comment, "invalid transfers") - suite.Require().Equal(sdk.MsgTypeURL(&types.MsgSend{}), sdk.MsgTypeURL(&msg)) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateMsgMultiSendToModuleAccount() { - const mAccount = 2 - - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 2) - - // execute operation - op := simulation.SimulateMsgMultiSendToModuleAccount(suite.txConfig, suite.accountKeeper, suite.bankKeeper, mAccount) - - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().Error(err) - - var msg types.MsgMultiSend - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().False(operationMsg.OK) // sending tokens to a module account should fail - suite.Require().Equal(operationMsg.Comment, "invalid transfers") - suite.Require().Equal(sdk.MsgTypeURL(&types.MsgMultiSend{}), sdk.MsgTypeURL(&msg)) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { - accounts := simtypes.RandomAccounts(r, n) - - initAmt := sdk.TokensFromConsensusPower(200, sdk.DefaultPowerReduction) - initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) - - // add coins to the accounts - for _, account := range accounts { - acc := suite.accountKeeper.NewAccountWithAddress(suite.ctx, account.Address) - suite.accountKeeper.SetAccount(suite.ctx, acc) - suite.Require().NoError(testutil.FundAccount(suite.ctx, suite.bankKeeper, account.Address, initCoins)) - } - - return accounts -} - -func TestSimTestSuite(t *testing.T) { - suite.Run(t, new(SimTestSuite)) -} diff --git a/tests/sims/distribution/app_config.go b/tests/sims/distribution/app_config.go deleted file mode 100644 index 989d74b74f5a..000000000000 --- a/tests/sims/distribution/app_config.go +++ /dev/null @@ -1,29 +0,0 @@ -package distribution - -import ( - _ "cosmossdk.io/x/accounts" // import as blank for app wiring - _ "cosmossdk.io/x/bank" // import as blank for app wiring - _ "cosmossdk.io/x/consensus" // import as blank for app wiring - _ "cosmossdk.io/x/distribution" // import as blank for app wiring - _ "cosmossdk.io/x/mint" // import as blank for app wiring - _ "cosmossdk.io/x/protocolpool" // import as blank for app wiring - _ "cosmossdk.io/x/staking" // import as blank for app wiring - - "github.com/cosmos/cosmos-sdk/testutil/configurator" - _ "github.com/cosmos/cosmos-sdk/x/auth" // import as blank for app wiring - _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" // import as blank for app wiring - _ "github.com/cosmos/cosmos-sdk/x/genutil" // import as blank for app wiring -) - -var AppConfig = configurator.NewAppConfig( - configurator.AccountsModule(), - configurator.AuthModule(), - configurator.BankModule(), - configurator.StakingModule(), - configurator.TxModule(), - configurator.ConsensusModule(), - configurator.GenutilModule(), - configurator.DistributionModule(), - configurator.MintModule(), - configurator.ProtocolPoolModule(), -) diff --git a/tests/sims/distribution/operations_test.go b/tests/sims/distribution/operations_test.go deleted file mode 100644 index 972217c6cf37..000000000000 --- a/tests/sims/distribution/operations_test.go +++ /dev/null @@ -1,287 +0,0 @@ -package distribution - -import ( - "math/rand" - "testing" - - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/suite" - - "cosmossdk.io/collections" - "cosmossdk.io/depinject" - "cosmossdk.io/log" - "cosmossdk.io/math" - bankkeeper "cosmossdk.io/x/bank/keeper" - banktestutil "cosmossdk.io/x/bank/testutil" - "cosmossdk.io/x/distribution/keeper" - "cosmossdk.io/x/distribution/simulation" - "cosmossdk.io/x/distribution/types" - stakingkeeper "cosmossdk.io/x/staking/keeper" - stakingtypes "cosmossdk.io/x/staking/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/codec/address" - "github.com/cosmos/cosmos-sdk/runtime" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" -) - -// TestWeightedOperations tests the weights of the operations. -func (suite *SimTestSuite) TestWeightedOperations() { - appParams := make(simtypes.AppParams) - - weightedOps := simulation.WeightedOperations(appParams, suite.cdc, suite.txConfig, suite.accountKeeper, - suite.bankKeeper, suite.distrKeeper, suite.stakingKeeper) - - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accs := suite.getTestingAccounts(r, 3) - - expected := []struct { - weight int - opMsgRoute string - opMsgName string - }{ - {simulation.DefaultWeightMsgSetWithdrawAddress, types.ModuleName, sdk.MsgTypeURL(&types.MsgSetWithdrawAddress{})}, - {simulation.DefaultWeightMsgWithdrawDelegationReward, types.ModuleName, sdk.MsgTypeURL(&types.MsgWithdrawDelegatorReward{})}, - {simulation.DefaultWeightMsgWithdrawValidatorCommission, types.ModuleName, sdk.MsgTypeURL(&types.MsgWithdrawValidatorCommission{})}, - } - - for i, w := range weightedOps { - operationMsg, _, err := w.Op()(r, suite.app.BaseApp, suite.ctx, accs, "") - suite.Require().NoError(err) - - // the following checks are very much dependent from the ordering of the output given - // by WeightedOperations. if the ordering in WeightedOperations changes some tests - // will fail - suite.Require().Equal(expected[i].weight, w.Weight(), "weight should be the same") - suite.Require().Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") - suite.Require().Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") - } -} - -// TestSimulateMsgSetWithdrawAddress tests the normal scenario of a valid message of type TypeMsgSetWithdrawAddress. -// Abonormal scenarios, where the message is created by an errors, are not tested here. -func (suite *SimTestSuite) TestSimulateMsgSetWithdrawAddress() { - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 3) - - // execute operation - op := simulation.SimulateMsgSetWithdrawAddress(suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.distrKeeper) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg types.MsgSetWithdrawAddress - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.DelegatorAddress) - suite.Require().Equal("cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", msg.WithdrawAddress) - suite.Require().Equal(sdk.MsgTypeURL(&types.MsgSetWithdrawAddress{}), sdk.MsgTypeURL(&msg)) - suite.Require().Len(futureOperations, 0) -} - -// TestSimulateMsgWithdrawDelegatorReward tests the normal scenario of a valid message -// of type TypeMsgWithdrawDelegatorReward. -// Abonormal scenarios, where the message is created by an errors, are not tested here. -func (suite *SimTestSuite) TestSimulateMsgWithdrawDelegatorReward() { - // setup 3 accounts - s := rand.NewSource(4) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 3) - - // setup accounts[0] as validator - validator0 := suite.getTestingValidator0(accounts) - - // setup delegation - delTokens := sdk.TokensFromConsensusPower(2, sdk.DefaultPowerReduction) - validator0, issuedShares := validator0.AddTokensFromDel(delTokens) - delegator := accounts[1] - delegation := stakingtypes.NewDelegation(delegator.Address.String(), validator0.GetOperator(), issuedShares) - suite.Require().NoError(suite.stakingKeeper.SetDelegation(suite.ctx, delegation)) - valBz, err := address.NewBech32Codec("cosmosvaloper").StringToBytes(validator0.GetOperator()) - suite.Require().NoError(err) - - suite.Require().NoError(suite.distrKeeper.DelegatorStartingInfo.Set(suite.ctx, collections.Join(sdk.ValAddress(valBz), delegator.Address), types.NewDelegatorStartingInfo(2, math.LegacyOneDec(), 200))) - - suite.setupValidatorRewards(valBz) - - // execute operation - op := simulation.SimulateMsgWithdrawDelegatorReward(suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.distrKeeper, suite.stakingKeeper) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg types.MsgWithdrawDelegatorReward - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal("cosmosvaloper1l4s054098kk9hmr5753c6k3m2kw65h686d3mhr", msg.ValidatorAddress) - suite.Require().Equal("cosmos1d6u7zhjwmsucs678d7qn95uqajd4ucl9jcjt26", msg.DelegatorAddress) - suite.Require().Equal(sdk.MsgTypeURL(&types.MsgWithdrawDelegatorReward{}), sdk.MsgTypeURL(&msg)) - suite.Require().Len(futureOperations, 0) -} - -// TestSimulateMsgWithdrawValidatorCommission tests the normal scenario of a valid message -// of type TypeMsgWithdrawValidatorCommission. -// Abonormal scenarios, where the message is created by an errors, are not tested here. -func (suite *SimTestSuite) TestSimulateMsgWithdrawValidatorCommission() { - suite.testSimulateMsgWithdrawValidatorCommission("atoken") - suite.testSimulateMsgWithdrawValidatorCommission("tokenxxx") -} - -// all the checks in this function should not fail if we change the tokenName -func (suite *SimTestSuite) testSimulateMsgWithdrawValidatorCommission(tokenName string) { - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 3) - - // setup accounts[0] as validator - validator0 := suite.getTestingValidator0(accounts) - - // set module account coins - distrAcc := suite.distrKeeper.GetDistributionAccount(suite.ctx) - suite.Require().NoError(banktestutil.FundModuleAccount(suite.ctx, suite.bankKeeper, distrAcc.GetName(), sdk.NewCoins( - sdk.NewCoin(tokenName, math.NewInt(10)), - sdk.NewCoin("stake", math.NewInt(5)), - ))) - suite.accountKeeper.SetModuleAccount(suite.ctx, distrAcc) - - // set outstanding rewards - valCommission := sdk.NewDecCoins( - sdk.NewDecCoinFromDec(tokenName, math.LegacyNewDec(5).Quo(math.LegacyNewDec(2))), - sdk.NewDecCoinFromDec("stake", math.LegacyNewDec(1).Quo(math.LegacyNewDec(1))), - ) - valCodec := address.NewBech32Codec("cosmosvaloper") - - val0, err := valCodec.StringToBytes(validator0.GetOperator()) - suite.Require().NoError(err) - - genVal0, err := valCodec.StringToBytes(suite.genesisVals[0].GetOperator()) - suite.Require().NoError(err) - - suite.Require().NoError(suite.distrKeeper.ValidatorOutstandingRewards.Set(suite.ctx, val0, types.ValidatorOutstandingRewards{Rewards: valCommission})) - suite.Require().NoError(suite.distrKeeper.ValidatorOutstandingRewards.Set(suite.ctx, genVal0, types.ValidatorOutstandingRewards{Rewards: valCommission})) - - // setup validator accumulated commission - suite.Require().NoError(suite.distrKeeper.ValidatorsAccumulatedCommission.Set(suite.ctx, val0, types.ValidatorAccumulatedCommission{Commission: valCommission})) - suite.Require().NoError(suite.distrKeeper.ValidatorsAccumulatedCommission.Set(suite.ctx, genVal0, types.ValidatorAccumulatedCommission{Commission: valCommission})) - - // execute operation - op := simulation.SimulateMsgWithdrawValidatorCommission(suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.distrKeeper, suite.stakingKeeper) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - if !operationMsg.OK { - suite.Require().Equal("could not find account", operationMsg.Comment) - } else { - suite.Require().NoError(err) - - var msg types.MsgWithdrawValidatorCommission - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal("cosmosvaloper1tnh2q55v8wyygtt9srz5safamzdengsn9dsd7z", msg.ValidatorAddress) - suite.Require().Equal(sdk.MsgTypeURL(&types.MsgWithdrawValidatorCommission{}), sdk.MsgTypeURL(&msg)) - suite.Require().Len(futureOperations, 0) - } -} - -type SimTestSuite struct { - suite.Suite - - ctx sdk.Context - app *runtime.App - genesisVals []stakingtypes.Validator - - txConfig client.TxConfig - cdc codec.Codec - stakingKeeper *stakingkeeper.Keeper - accountKeeper authkeeper.AccountKeeper - bankKeeper bankkeeper.Keeper - distrKeeper keeper.Keeper -} - -func (suite *SimTestSuite) SetupTest() { - var ( - appBuilder *runtime.AppBuilder - err error - ) - suite.app, err = simtestutil.Setup( - depinject.Configs( - AppConfig, - depinject.Supply(log.NewNopLogger()), - ), - &suite.accountKeeper, - &suite.bankKeeper, - &suite.cdc, - &appBuilder, - &suite.stakingKeeper, - &suite.distrKeeper, - &suite.txConfig, - ) - - suite.NoError(err) - - suite.ctx = suite.app.BaseApp.NewContext(false) - - genesisVals, err := suite.stakingKeeper.GetAllValidators(suite.ctx) - suite.Require().NoError(err) - suite.Require().Len(genesisVals, 1) - suite.genesisVals = genesisVals -} - -func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { - accounts := simtypes.RandomAccounts(r, n) - - initAmt := suite.stakingKeeper.TokensFromConsensusPower(suite.ctx, 200) - initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) - - // add coins to the accounts - for _, account := range accounts { - acc := suite.accountKeeper.NewAccountWithAddress(suite.ctx, account.Address) - suite.accountKeeper.SetAccount(suite.ctx, acc) - suite.Require().NoError(banktestutil.FundAccount(suite.ctx, suite.bankKeeper, account.Address, initCoins)) - } - - return accounts -} - -func (suite *SimTestSuite) getTestingValidator0(accounts []simtypes.Account) stakingtypes.Validator { - commission0 := stakingtypes.NewCommission(math.LegacyZeroDec(), math.LegacyOneDec(), math.LegacyOneDec()) - return suite.getTestingValidator(accounts, commission0, 0) -} - -func (suite *SimTestSuite) getTestingValidator(accounts []simtypes.Account, commission stakingtypes.Commission, n int) stakingtypes.Validator { - require := suite.Require() - account := accounts[n] - valPubKey := account.PubKey - valAddr := sdk.ValAddress(account.PubKey.Address().Bytes()) - validator, err := stakingtypes.NewValidator(valAddr.String(), valPubKey, stakingtypes.Description{}) - require.NoError(err) - validator, err = validator.SetInitialCommission(commission) - require.NoError(err) - validator.DelegatorShares = math.LegacyNewDec(100) - validator.Tokens = math.NewInt(1000000) - - suite.Require().NoError(suite.stakingKeeper.SetValidator(suite.ctx, validator)) - - return validator -} - -func (suite *SimTestSuite) setupValidatorRewards(valAddress sdk.ValAddress) { - decCoins := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, math.LegacyOneDec())} - historicalRewards := types.NewValidatorHistoricalRewards(decCoins, 2) - suite.Require().NoError(suite.distrKeeper.ValidatorHistoricalRewards.Set(suite.ctx, collections.Join(valAddress, uint64(2)), historicalRewards)) - // setup current revards - currentRewards := types.NewValidatorCurrentRewards(decCoins, 3) - suite.Require().NoError(suite.distrKeeper.ValidatorCurrentRewards.Set(suite.ctx, valAddress, currentRewards)) -} - -func TestSimTestSuite(t *testing.T) { - suite.Run(t, new(SimTestSuite)) -} diff --git a/tests/sims/feegrant/operations_test.go b/tests/sims/feegrant/operations_test.go deleted file mode 100644 index 11b4f39615c8..000000000000 --- a/tests/sims/feegrant/operations_test.go +++ /dev/null @@ -1,213 +0,0 @@ -package simulation_test - -import ( - "math/rand" - "testing" - "time" - - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/suite" - - "cosmossdk.io/core/header" - "cosmossdk.io/depinject" - "cosmossdk.io/log" - _ "cosmossdk.io/x/accounts" // import as blank for app wiring - _ "cosmossdk.io/x/bank" - bankkeeper "cosmossdk.io/x/bank/keeper" - banktestutil "cosmossdk.io/x/bank/testutil" - _ "cosmossdk.io/x/consensus" - "cosmossdk.io/x/feegrant" - "cosmossdk.io/x/feegrant/keeper" - _ "cosmossdk.io/x/feegrant/module" - "cosmossdk.io/x/feegrant/simulation" - _ "cosmossdk.io/x/mint" - _ "cosmossdk.io/x/staking" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/runtime" - "github.com/cosmos/cosmos-sdk/testutil/configurator" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - _ "github.com/cosmos/cosmos-sdk/x/auth" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" - _ "github.com/cosmos/cosmos-sdk/x/genutil" -) - -type SimTestSuite struct { - suite.Suite - - app *runtime.App - ctx sdk.Context - feegrantKeeper keeper.Keeper - interfaceRegistry codectypes.InterfaceRegistry - txConfig client.TxConfig - accountKeeper authkeeper.AccountKeeper - bankKeeper bankkeeper.Keeper - cdc codec.Codec -} - -func (suite *SimTestSuite) SetupTest() { - var err error - suite.app, err = simtestutil.Setup( - depinject.Configs( - configurator.NewAppConfig( - configurator.AccountsModule(), - configurator.AuthModule(), - configurator.BankModule(), - configurator.StakingModule(), - configurator.TxModule(), - configurator.ConsensusModule(), - configurator.GenutilModule(), - configurator.FeegrantModule(), - ), - depinject.Supply(log.NewNopLogger()), - ), - &suite.feegrantKeeper, - &suite.bankKeeper, - &suite.accountKeeper, - &suite.interfaceRegistry, - &suite.txConfig, - &suite.cdc, - ) - suite.Require().NoError(err) - - suite.ctx = suite.app.BaseApp.NewContext(false).WithHeaderInfo(header.Info{Time: time.Now()}) -} - -func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { - accounts := simtypes.RandomAccounts(r, n) - initAmt := sdk.TokensFromConsensusPower(200, sdk.DefaultPowerReduction) - initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) - - // add coins to the accounts - for _, account := range accounts { - acc := suite.accountKeeper.NewAccountWithAddress(suite.ctx, account.Address) - suite.accountKeeper.SetAccount(suite.ctx, acc) - err := banktestutil.FundAccount(suite.ctx, suite.bankKeeper, account.Address, initCoins) - suite.Require().NoError(err) - } - - return accounts -} - -func (suite *SimTestSuite) TestWeightedOperations() { - require := suite.Require() - - appParams := make(simtypes.AppParams) - - weightedOps := simulation.WeightedOperations( - appParams, suite.txConfig, suite.accountKeeper, - suite.bankKeeper, suite.feegrantKeeper, - ) - - s := rand.NewSource(1) - r := rand.New(s) - accs := suite.getTestingAccounts(r, 3) - - expected := []struct { - weight int - opMsgRoute string - opMsgName string - }{ - { - simulation.DefaultWeightGrantAllowance, - feegrant.ModuleName, - sdk.MsgTypeURL(&feegrant.MsgGrantAllowance{}), - }, - { - simulation.DefaultWeightRevokeAllowance, - feegrant.ModuleName, - sdk.MsgTypeURL(&feegrant.MsgRevokeAllowance{}), - }, - } - - for i, w := range weightedOps { - operationMsg, _, err := w.Op()(r, suite.app.BaseApp, suite.ctx.WithHeaderInfo(header.Info{Time: time.Now()}), accs, suite.ctx.ChainID()) - require.NoError(err) - - // the following checks are very much dependent from the ordering of the output given - // by WeightedOperations. if the ordering in WeightedOperations changes some tests - // will fail - require.Equal(expected[i].weight, w.Weight(), "weight should be the same") - require.Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") - require.Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") - } -} - -func (suite *SimTestSuite) TestSimulateMsgGrantAllowance() { - app, ctx := suite.app, suite.ctx - require := suite.Require() - - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 3) - addr1, err := suite.accountKeeper.AddressCodec().BytesToString(accounts[1].Address) - require.NoError(err) - addr2, err := suite.accountKeeper.AddressCodec().BytesToString(accounts[2].Address) - require.NoError(err) - - // execute operation - op := simulation.SimulateMsgGrantAllowance(suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.feegrantKeeper) - operationMsg, futureOperations, err := op(r, app.BaseApp, ctx.WithHeaderInfo(header.Info{Time: time.Now()}), accounts, "") - require.NoError(err) - - var msg feegrant.MsgGrantAllowance - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(err) - require.True(operationMsg.OK) - require.Equal(addr2, msg.Granter) - require.Equal(addr1, msg.Grantee) - require.Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateMsgRevokeAllowance() { - app, ctx := suite.app, suite.ctx - require := suite.Require() - - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 3) - - feeAmt := sdk.TokensFromConsensusPower(200000, sdk.DefaultPowerReduction) - feeCoins := sdk.NewCoins(sdk.NewCoin("foo", feeAmt)) - - granter, grantee := accounts[0], accounts[1] - - oneYear := ctx.HeaderInfo().Time.AddDate(1, 0, 0) - err := suite.feegrantKeeper.GrantAllowance( - ctx, - granter.Address, - grantee.Address, - &feegrant.BasicAllowance{ - SpendLimit: feeCoins, - Expiration: &oneYear, - }, - ) - require.NoError(err) - - granterStr, err := suite.accountKeeper.AddressCodec().BytesToString(accounts[0].Address) - require.NoError(err) - granteeStr, err := suite.accountKeeper.AddressCodec().BytesToString(accounts[1].Address) - require.NoError(err) - - // execute operation - op := simulation.SimulateMsgRevokeAllowance(suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.feegrantKeeper) - operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") - require.NoError(err) - - var msg feegrant.MsgRevokeAllowance - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(err) - require.True(operationMsg.OK) - require.Equal(granterStr, msg.Granter) - require.Equal(granteeStr, msg.Grantee) - require.Len(futureOperations, 0) -} - -func TestSimTestSuite(t *testing.T) { - suite.Run(t, new(SimTestSuite)) -} diff --git a/tests/sims/gov/operations_test.go b/tests/sims/gov/operations_test.go deleted file mode 100644 index 95f99f33ab19..000000000000 --- a/tests/sims/gov/operations_test.go +++ /dev/null @@ -1,428 +0,0 @@ -package simulation_test - -import ( - "context" - "fmt" - "math/rand" - "testing" - "time" - - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/require" - - "cosmossdk.io/core/address" - "cosmossdk.io/core/header" - "cosmossdk.io/depinject" - "cosmossdk.io/log" - _ "cosmossdk.io/x/accounts" - _ "cosmossdk.io/x/bank" - bankkeeper "cosmossdk.io/x/bank/keeper" - "cosmossdk.io/x/bank/testutil" - _ "cosmossdk.io/x/consensus" - _ "cosmossdk.io/x/gov" - "cosmossdk.io/x/gov/keeper" - "cosmossdk.io/x/gov/simulation" - "cosmossdk.io/x/gov/types" - v1 "cosmossdk.io/x/gov/types/v1" - "cosmossdk.io/x/gov/types/v1beta1" - _ "cosmossdk.io/x/protocolpool" - _ "cosmossdk.io/x/staking" - stakingkeeper "cosmossdk.io/x/staking/keeper" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/runtime" - "github.com/cosmos/cosmos-sdk/testutil/configurator" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - _ "github.com/cosmos/cosmos-sdk/x/auth" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" -) - -var ( - _ simtypes.WeightedProposalMsg = MockWeightedProposals{} - _ simtypes.WeightedProposalContent = MockWeightedProposals{} //nolint:staticcheck // testing legacy code path -) - -type MockWeightedProposals struct { - n int -} - -func (m MockWeightedProposals) AppParamsKey() string { - return fmt.Sprintf("AppParamsKey-%d", m.n) -} - -func (m MockWeightedProposals) DefaultWeight() int { - return m.n -} - -func (m MockWeightedProposals) MsgSimulatorFn() simtypes.MsgSimulatorFnX { - return func(_ context.Context, r *rand.Rand, _ []simtypes.Account, _ address.Codec) (sdk.Msg, error) { - return nil, nil - } -} - -func (m MockWeightedProposals) ContentSimulatorFn() simtypes.ContentSimulatorFn { //nolint:staticcheck // testing legacy code path - return func(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) simtypes.Content { //nolint:staticcheck // testing legacy code path - return v1beta1.NewTextProposal( - fmt.Sprintf("title-%d: %s", m.n, simtypes.RandStringOfLength(r, 100)), - fmt.Sprintf("description-%d: %s", m.n, simtypes.RandStringOfLength(r, 4000)), - ) - } -} - -func mockWeightedProposalMsg(n int) []simtypes.WeightedProposalMsg { - wpc := make([]simtypes.WeightedProposalMsg, n) - for i := 0; i < n; i++ { - wpc[i] = MockWeightedProposals{i} - } - return wpc -} - -func mockWeightedLegacyProposalContent(n int) []simtypes.WeightedProposalContent { //nolint:staticcheck // testing legacy code path - wpc := make([]simtypes.WeightedProposalContent, n) //nolint:staticcheck // testing legacy code path - for i := 0; i < n; i++ { - wpc[i] = MockWeightedProposals{i} - } - return wpc -} - -// TestWeightedOperations tests the weights of the operations. -func TestWeightedOperations(t *testing.T) { - suite, ctx := createTestSuite(t, false) - app := suite.App - ctx.WithChainID("test-chain") - appParams := make(simtypes.AppParams) - - weightesOps := simulation.WeightedOperations(appParams, suite.TxConfig, suite.AccountKeeper, - suite.BankKeeper, suite.GovKeeper, mockWeightedProposalMsg(3), mockWeightedLegacyProposalContent(1), - ) - - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accs := getTestingAccounts(t, r, suite.AccountKeeper, suite.BankKeeper, suite.StakingKeeper, ctx, 3) - - expected := []struct { - weight int - opMsgRoute string - opMsgName string - }{ - {simulation.DefaultWeightMsgDeposit, types.ModuleName, simulation.TypeMsgDeposit}, - {simulation.DefaultWeightMsgVote, types.ModuleName, simulation.TypeMsgVote}, - {simulation.DefaultWeightMsgVoteWeighted, types.ModuleName, simulation.TypeMsgVoteWeighted}, - {simulation.DefaultWeightMsgCancelProposal, types.ModuleName, simulation.TypeMsgCancelProposal}, - {0, types.ModuleName, simulation.TypeMsgSubmitProposal}, - {1, types.ModuleName, simulation.TypeMsgSubmitProposal}, - {2, types.ModuleName, simulation.TypeMsgSubmitProposal}, - {0, types.ModuleName, simulation.TypeMsgSubmitProposal}, - } - - require.Equal(t, len(weightesOps), len(expected), "number of operations should be the same") - for i, w := range weightesOps { - operationMsg, _, err := w.Op()(r, app.BaseApp, ctx, accs, ctx.ChainID()) - require.NoError(t, err) - - // the following checks are very much dependent from the ordering of the output given - // by WeightedOperations. if the ordering in WeightedOperations changes some tests - // will fail - require.Equal(t, expected[i].weight, w.Weight(), "weight should be the same") - require.Equal(t, expected[i].opMsgRoute, operationMsg.Route, "route should be the same") - require.Equal(t, expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") - } -} - -// TestSimulateMsgSubmitProposal tests the normal scenario of a valid message of type TypeMsgSubmitProposal. -// Abnormal scenarios, where errors occur, are not tested here. -func TestSimulateMsgSubmitProposal(t *testing.T) { - suite, ctx := createTestSuite(t, false) - app := suite.App - - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accounts := getTestingAccounts(t, r, suite.AccountKeeper, suite.BankKeeper, suite.StakingKeeper, ctx, 3) - - // execute operation - op := simulation.SimulateMsgSubmitProposal(suite.TxConfig, suite.AccountKeeper, suite.BankKeeper, suite.GovKeeper, MockWeightedProposals{3}.MsgSimulatorFn()) - operationMsg, _, err := op(r, app.BaseApp, ctx, accounts, "") - require.NoError(t, err) - - var msg v1.MsgSubmitProposal - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(t, err) - require.True(t, operationMsg.OK) - require.Equal(t, "cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Proposer) - require.NotEqual(t, len(msg.InitialDeposit), 0) - require.Equal(t, "47841094stake", msg.InitialDeposit[0].String()) - require.Equal(t, simulation.TypeMsgSubmitProposal, sdk.MsgTypeURL(&msg)) -} - -// TestSimulateMsgSubmitLegacyProposal tests the normal scenario of a valid message of type TypeMsgSubmitProposal. -// Abnormal scenarios, where errors occur, are not tested here. -func TestSimulateMsgSubmitLegacyProposal(t *testing.T) { - suite, ctx := createTestSuite(t, false) - app := suite.App - - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accounts := getTestingAccounts(t, r, suite.AccountKeeper, suite.BankKeeper, suite.StakingKeeper, ctx, 3) - - // execute operation - op := simulation.SimulateMsgSubmitLegacyProposal(suite.TxConfig, suite.AccountKeeper, suite.BankKeeper, suite.GovKeeper, MockWeightedProposals{3}.ContentSimulatorFn()) - operationMsg, _, err := op(r, app.BaseApp, ctx, accounts, "") - require.NoError(t, err) - - var msg v1.MsgSubmitProposal - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(t, err) - var msgLegacyContent v1.MsgExecLegacyContent - err = proto.Unmarshal(msg.Messages[0].Value, &msgLegacyContent) - require.NoError(t, err) - var textProposal v1beta1.TextProposal - err = proto.Unmarshal(msgLegacyContent.Content.Value, &textProposal) - require.NoError(t, err) - - require.True(t, operationMsg.OK) - require.Equal(t, "cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", msg.Proposer) - require.NotEqual(t, len(msg.InitialDeposit), 0) - require.Equal(t, "25166256stake", msg.InitialDeposit[0].String()) - require.Equal(t, "title-3: ZBSpYuLyYggwexjxusrBqDOTtGTOWeLrQKjLxzIivHSlcxgdXhhuTSkuxKGLwQvuyNhYFmBZHeAerqyNEUzXPFGkqEGqiQWIXnku", - textProposal.GetTitle()) - require.Equal(t, "description-3: NJWzHdBNpAXKJPHWQdrGYcAHSctgVlqwqHoLfHsXUdStwfefwzqLuKEhmMyYLdbZrcPgYqjNHxPexsruwEGStAneKbWkQDDIlCWBLSiAASNhZqNFlPtfqPJoxKsgMdzjWqLWdqKQuJqWPMvwPQWZUtVMOTMYKJbfdlZsjdsomuScvDmbDkgRualsxDvRJuCAmPOXitIbcyWsKGSdrEunFAOdmXnsuyFVgJqEjbklvmwrUlsxjRSfKZxGcpayDdgoFcnVSutxjRgOSFzPwidAjubMncNweqpbxhXGchpZUxuFDOtpnhNUycJICRYqsPhPSCjPTWZFLkstHWJxvdPEAyEIxXgLwbNOjrgzmaujiBABBIXvcXpLrbcEWNNQsbjvgJFgJkflpRohHUutvnaUqoopuKjTDaemDeSdqbnOzcfJpcTuAQtZoiLZOoAIlboFDAeGmSNwkvObPRvRWQgWkGkxwtPauYgdkmypLjbqhlHJIQTntgWjXwZdOyYEdQRRLfMSdnxqppqUofqLbLQDUjwKVKfZJUJQPsWIPwIVaSTrmKskoAhvmZyJgeRpkaTfGgrJzAigcxtfshmiDCFkuiluqtMOkidknnTBtumyJYlIsWLnCQclqdVmikUoMOPdPWwYbJxXyqUVicNxFxyqJTenNblyyKSdlCbiXxUiYUiMwXZASYfvMDPFgxniSjWaZTjHkqlJvtBsXqwPpyVxnJVGFWhfSxgOcduoxkiopJvFjMmFabrGYeVtTXLhxVUEiGwYUvndjFGzDVntUvibiyZhfMQdMhgsiuysLMiePBNXifRLMsSmXPkwlPloUbJveCvUlaalhZHuvdkCnkSHbMbmOnrfEGPwQiACiPlnihiaOdbjPqPiTXaHDoJXjSlZmltGqNHHNrcKdlFSCdmVOuvDcBLdSklyGJmcLTbSFtALdGlPkqqecJrpLCXNPWefoTJNgEJlyMEPneVaxxduAAEqQpHWZodWyRkDAxzyMnFMcjSVqeRXLqsNyNtQBbuRvunZflWSbbvXXdkyLikYqutQhLPONXbvhcQZJPSWnOulqQaXmbfFxAkqfYeseSHOQidHwbcsOaMnSrrmGjjRmEMQNuknupMxJiIeVjmgZvbmjPIQTEhQFULQLBMPrxcFPvBinaOPYWGvYGRKxLZdwamfRQQFngcdSlvwjfaPbURasIsGJVHtcEAxnIIrhSriiXLOlbEBLXFElXJFGxHJczRBIxAuPKtBisjKBwfzZFagdNmjdwIRvwzLkFKWRTDPxJCmpzHUcrPiiXXHnOIlqNVoGSXZewdnCRhuxeYGPVTfrNTQNOxZmxInOazUYNTNDgzsxlgiVEHPKMfbesvPHUqpNkUqbzeuzfdrsuLDpKHMUbBMKczKKWOdYoIXoPYtEjfOnlQLoGnbQUCuERdEFaptwnsHzTJDsuZkKtzMpFaZobynZdzNydEeJJHDYaQcwUxcqvwfWwNUsCiLvkZQiSfzAHftYgAmVsXgtmcYgTqJIawstRYJrZdSxlfRiqTufgEQVambeZZmaAyRQbcmdjVUZZCgqDrSeltJGXPMgZnGDZqISrGDOClxXCxMjmKqEPwKHoOfOeyGmqWqihqjINXLqnyTesZePQRqaWDQNqpLgNrAUKulklmckTijUltQKuWQDwpLmDyxLppPVMwsmBIpOwQttYFMjgJQZLYFPmxWFLIeZihkRNnkzoypBICIxgEuYsVWGIGRbbxqVasYnstWomJnHwmtOhAFSpttRYYzBmyEtZXiCthvKvWszTXDbiJbGXMcrYpKAgvUVFtdKUfvdMfhAryctklUCEdjetjuGNfJjajZtvzdYaqInKtFPPLYmRaXPdQzxdSQfmZDEVHlHGEGNSPRFJuIfKLLfUmnHxHnRjmzQPNlqrXgifUdzAGKVabYqvcDeYoTYgPsBUqehrBhmQUgTvDnsdpuhUoxskDdppTsYMcnDIPSwKIqhXDCIxOuXrywahvVavvHkPuaenjLmEbMgrkrQLHEAwrhHkPRNvonNQKqprqOFVZKAtpRSpvQUxMoXCMZLSSbnLEFsjVfANdQNQVwTmGxqVjVqRuxREAhuaDrFgEZpYKhwWPEKBevBfsOIcaZKyykQafzmGPLRAKDtTcJxJVgiiuUkmyMYuDUNEUhBEdoBLJnamtLmMJQgmLiUELIhLpiEvpOXOvXCPUeldLFqkKOwfacqIaRcnnZvERKRMCKUkMABbDHytQqQblrvoxOZkwzosQfDKGtIdfcXRJNqlBNwOCWoQBcEWyqrMlYZIAXYJmLfnjoJepgSFvrgajaBAIksoyeHqgqbGvpAstMIGmIhRYGGNPRIfOQKsGoKgxtsidhTaAePRCBFqZgPDWCIkqOJezGVkjfYUCZTlInbxBXwUAVRsxHTQtJFnnpmMvXDYCVlEmnZBKhmmxQOIQzxFWpJQkQoSAYzTEiDWEOsVLNrbfzeHFRyeYATakQQWmFDLPbVMCJcWjFGJjfqCoVzlbNNEsqxdSmNPjTjHYOkuEMFLkXYGaoJlraLqayMeCsTjWNRDPBywBJLAPVkGQqTwApVVwYAetlwSbzsdHWsTwSIcctkyKDuRWYDQikRqsKTMJchrliONJeaZIzwPQrNbTwxsGdwuduvibtYndRwpdsvyCktRHFalvUuEKMqXbItfGcNGWsGzubdPMYayOUOINjpcFBeESdwpdlTYmrPsLsVDhpTzoMegKrytNVZkfJRPuDCUXxSlSthOohmsuxmIZUedzxKmowKOdXTMcEtdpHaPWgIsIjrViKrQOCONlSuazmLuCUjLltOGXeNgJKedTVrrVCpWYWHyVrdXpKgNaMJVjbXxnVMSChdWKuZdqpisvrkBJPoURDYxWOtpjzZoOpWzyUuYNhCzRoHsMjmmWDcXzQiHIyjwdhPNwiPqFxeUfMVFQGImhykFgMIlQEoZCaRoqSBXTSWAeDumdbsOGtATwEdZlLfoBKiTvodQBGOEcuATWXfiinSjPmJKcWgQrTVYVrwlyMWhxqNbCMpIQNoSMGTiWfPTCezUjYcdWppnsYJihLQCqbNLRGgqrwHuIvsazapTpoPZIyZyeeSueJuTIhpHMEJfJpScshJubJGfkusuVBgfTWQoywSSliQQSfbvaHKiLnyjdSbpMkdBgXepoSsHnCQaYuHQqZsoEOmJCiuQUpJkmfyfbIShzlZpHFmLCsbknEAkKXKfRTRnuwdBeuOGgFbJLbDksHVapaRayWzwoYBEpmrlAxrUxYMUekKbpjPNfjUCjhbdMAnJmYQVZBQZkFVweHDAlaqJjRqoQPoOMLhyvYCzqEuQsAFoxWrzRnTVjStPadhsESlERnKhpEPsfDxNvxqcOyIulaCkmPdambLHvGhTZzysvqFauEgkFRItPfvisehFmoBhQqmkfbHVsgfHXDPJVyhwPllQpuYLRYvGodxKjkarnSNgsXoKEMlaSKxKdcVgvOkuLcfLFfdtXGTclqfPOfeoVLbqcjcXCUEBgAGplrkgsmIEhWRZLlGPGCwKWRaCKMkBHTAcypUrYjWwCLtOPVygMwMANGoQwFnCqFrUGMCRZUGJKTZIGPyldsifauoMnJPLTcDHmilcmahlqOELaAUYDBuzsVywnDQfwRLGIWozYaOAilMBcObErwgTDNGWnwQMUgFFSKtPDMEoEQCTKVREqrXZSGLqwTMcxHfWotDllNkIJPMbXzjDVjPOOjCFuIvTyhXKLyhUScOXvYthRXpPfKwMhptXaxIxgqBoUqzrWbaoLTVpQoottZyPFfNOoMioXHRuFwMRYUiKvcWPkrayyTLOCFJlAyslDameIuqVAuxErqFPEWIScKpBORIuZqoXlZuTvAjEdlEWDODFRregDTqGNoFBIHxvimmIZwLfFyKUfEWAnNBdtdzDmTPXtpHRGdIbuucfTjOygZsTxPjfweXhSUkMhPjMaxKlMIJMOXcnQfyzeOcbWwNbeH", - textProposal.GetDescription()) - require.Equal(t, simulation.TypeMsgSubmitProposal, sdk.MsgTypeURL(&msg)) -} - -// TestSimulateMsgCancelProposal tests the normal scenario of a valid message of type TypeMsgCancelProposal. -// Abnormal scenarios, where errors occur, are not tested here. -func TestSimulateMsgCancelProposal(t *testing.T) { - suite, ctx := createTestSuite(t, false) - app := suite.App - blockTime := time.Now().UTC() - ctx = ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accounts := getTestingAccounts(t, r, suite.AccountKeeper, suite.BankKeeper, suite.StakingKeeper, ctx, 3) - // setup a proposal - proposer, err := suite.AccountKeeper.AddressCodec().BytesToString(accounts[0].Address) - require.NoError(t, err) - content := v1beta1.NewTextProposal("Test", "description") - contentMsg, err := v1.NewLegacyContent(content, suite.GovKeeper.GetGovernanceAccount(ctx).GetAddress().String()) - require.NoError(t, err) - - submitTime := ctx.HeaderInfo().Time - params, _ := suite.GovKeeper.Params.Get(ctx) - depositPeriod := params.MaxDepositPeriod - - proposal, err := v1.NewProposal([]sdk.Msg{contentMsg}, 1, submitTime, submitTime.Add(*depositPeriod), "", "title", "summary", proposer, v1.ProposalType_PROPOSAL_TYPE_STANDARD) - require.NoError(t, err) - - err = suite.GovKeeper.Proposals.Set(ctx, proposal.Id, proposal) - require.NoError(t, err) - - // execute operation - op := simulation.SimulateMsgCancelProposal(suite.TxConfig, suite.AccountKeeper, suite.BankKeeper, suite.GovKeeper) - operationMsg, _, err := op(r, app.BaseApp, ctx, accounts, "") - require.NoError(t, err) - - var msg v1.MsgCancelProposal - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(t, err) - require.NoError(t, err) - require.True(t, operationMsg.OK) - require.Equal(t, uint64(1), msg.ProposalId) - require.Equal(t, proposer, msg.Proposer) - require.Equal(t, simulation.TypeMsgCancelProposal, sdk.MsgTypeURL(&msg)) -} - -// TestSimulateMsgDeposit tests the normal scenario of a valid message of type TypeMsgDeposit. -// Abnormal scenarios, where errors occur, are not tested here. -func TestSimulateMsgDeposit(t *testing.T) { - suite, ctx := createTestSuite(t, false) - app := suite.App - blockTime := time.Now().UTC() - ctx = ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accounts := getTestingAccounts(t, r, suite.AccountKeeper, suite.BankKeeper, suite.StakingKeeper, ctx, 3) - - // setup a proposal - content := v1beta1.NewTextProposal("Test", "description") - contentMsg, err := v1.NewLegacyContent(content, suite.GovKeeper.GetGovernanceAccount(ctx).GetAddress().String()) - require.NoError(t, err) - - submitTime := ctx.HeaderInfo().Time - params, _ := suite.GovKeeper.Params.Get(ctx) - depositPeriod := params.MaxDepositPeriod - - proposal, err := v1.NewProposal([]sdk.Msg{contentMsg}, 1, submitTime, submitTime.Add(*depositPeriod), "", "text proposal", "description", "cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", v1.ProposalType_PROPOSAL_TYPE_STANDARD) - require.NoError(t, err) - - err = suite.GovKeeper.Proposals.Set(ctx, proposal.Id, proposal) - require.NoError(t, err) - - // execute operation - op := simulation.SimulateMsgDeposit(suite.TxConfig, suite.AccountKeeper, suite.BankKeeper, suite.GovKeeper, simulation.NewSharedState()) - operationMsg, _, err := op(r, app.BaseApp, ctx, accounts, "") - require.NoError(t, err) - - var msg v1.MsgDeposit - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(t, err) - require.True(t, operationMsg.OK) - require.Equal(t, uint64(1), msg.ProposalId) - require.Equal(t, "cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Depositor) - require.NotEqual(t, len(msg.Amount), 0) - require.Equal(t, "560969stake", msg.Amount[0].String()) - require.Equal(t, simulation.TypeMsgDeposit, sdk.MsgTypeURL(&msg)) -} - -// TestSimulateMsgVote tests the normal scenario of a valid message of type TypeMsgVote. -// Abnormal scenarios, where errors occur, are not tested here. -func TestSimulateMsgVote(t *testing.T) { - suite, ctx := createTestSuite(t, false) - app := suite.App - blockTime := time.Now().UTC() - ctx = ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accounts := getTestingAccounts(t, r, suite.AccountKeeper, suite.BankKeeper, suite.StakingKeeper, ctx, 3) - - // setup a proposal - govAcc := suite.GovKeeper.GetGovernanceAccount(ctx).GetAddress().String() - contentMsg, err := v1.NewLegacyContent(v1beta1.NewTextProposal("Test", "description"), govAcc) - require.NoError(t, err) - - submitTime := ctx.HeaderInfo().Time - params, _ := suite.GovKeeper.Params.Get(ctx) - depositPeriod := params.MaxDepositPeriod - - proposal, err := v1.NewProposal([]sdk.Msg{contentMsg}, 1, submitTime, submitTime.Add(*depositPeriod), "", "text proposal", "description", "cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", v1.ProposalType_PROPOSAL_TYPE_STANDARD) - require.NoError(t, err) - - err = suite.GovKeeper.ActivateVotingPeriod(ctx, proposal) - require.NoError(t, err) - - // execute operation - op := simulation.SimulateMsgVote(suite.TxConfig, suite.AccountKeeper, suite.BankKeeper, suite.GovKeeper, simulation.NewSharedState()) - operationMsg, _, err := op(r, app.BaseApp, ctx, accounts, "") - require.NoError(t, err) - - var msg v1.MsgVote - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(t, err) - require.True(t, operationMsg.OK) - require.Equal(t, uint64(1), msg.ProposalId) - require.Equal(t, "cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Voter) - require.Equal(t, v1.OptionYes, msg.Option) - require.Equal(t, simulation.TypeMsgVote, sdk.MsgTypeURL(&msg)) -} - -// TestSimulateMsgVoteWeighted tests the normal scenario of a valid message of type TypeMsgVoteWeighted. -// Abnormal scenarios, where errors occur, are not tested here. -func TestSimulateMsgVoteWeighted(t *testing.T) { - suite, ctx := createTestSuite(t, false) - app := suite.App - blockTime := time.Now().UTC() - ctx = ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accounts := getTestingAccounts(t, r, suite.AccountKeeper, suite.BankKeeper, suite.StakingKeeper, ctx, 3) - - // setup a proposal - govAcc := suite.GovKeeper.GetGovernanceAccount(ctx).GetAddress().String() - contentMsg, err := v1.NewLegacyContent(v1beta1.NewTextProposal("Test", "description"), govAcc) - require.NoError(t, err) - submitTime := ctx.HeaderInfo().Time - params, _ := suite.GovKeeper.Params.Get(ctx) - depositPeriod := params.MaxDepositPeriod - - proposal, err := v1.NewProposal([]sdk.Msg{contentMsg}, 1, submitTime, submitTime.Add(*depositPeriod), "", "text proposal", "test", "cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", v1.ProposalType_PROPOSAL_TYPE_STANDARD) - require.NoError(t, err) - - err = suite.GovKeeper.ActivateVotingPeriod(ctx, proposal) - require.NoError(t, err) - - // execute operation - op := simulation.SimulateMsgVoteWeighted(suite.TxConfig, suite.AccountKeeper, suite.BankKeeper, suite.GovKeeper, simulation.NewSharedState()) - operationMsg, _, err := op(r, app.BaseApp, ctx, accounts, "") - require.NoError(t, err) - - var msg v1.MsgVoteWeighted - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(t, err) - require.True(t, operationMsg.OK) - require.Equal(t, uint64(1), msg.ProposalId) - require.Equal(t, "cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Voter) - require.True(t, len(msg.Options) >= 1) - require.Equal(t, simulation.TypeMsgVoteWeighted, sdk.MsgTypeURL(&msg)) -} - -type suite struct { - TxConfig client.TxConfig - AccountKeeper authkeeper.AccountKeeper - BankKeeper bankkeeper.Keeper - GovKeeper *keeper.Keeper - StakingKeeper *stakingkeeper.Keeper - App *runtime.App -} - -// returns context and an app with updated mint keeper -func createTestSuite(t *testing.T, isCheckTx bool) (suite, sdk.Context) { - t.Helper() - res := suite{} - - app, err := simtestutil.Setup( - depinject.Configs( - configurator.NewAppConfig( - configurator.AccountsModule(), - configurator.AuthModule(), - configurator.TxModule(), - configurator.BankModule(), - configurator.StakingModule(), - configurator.ConsensusModule(), - configurator.GovModule(), - configurator.ProtocolPoolModule(), - ), - depinject.Supply(log.NewNopLogger()), - ), - &res.TxConfig, &res.AccountKeeper, &res.BankKeeper, &res.GovKeeper, &res.StakingKeeper) - require.NoError(t, err) - - ctx := app.BaseApp.NewContext(isCheckTx) - - res.App = app - return res, ctx -} - -func getTestingAccounts( - t *testing.T, r *rand.Rand, - accountKeeper authkeeper.AccountKeeper, bankKeeper bankkeeper.Keeper, stakingKeeper *stakingkeeper.Keeper, - ctx sdk.Context, n int, -) []simtypes.Account { - t.Helper() - accounts := simtypes.RandomAccounts(r, n) - - initAmt := stakingKeeper.TokensFromConsensusPower(ctx, 200) - initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) - - // add coins to the accounts - for _, account := range accounts { - acc := accountKeeper.NewAccountWithAddress(ctx, account.Address) - accountKeeper.SetAccount(ctx, acc) - require.NoError(t, testutil.FundAccount(ctx, bankKeeper, account.Address, initCoins)) - } - - return accounts -} diff --git a/tests/sims/nft/app_config.go b/tests/sims/nft/app_config.go deleted file mode 100644 index 7b3a4bb869b3..000000000000 --- a/tests/sims/nft/app_config.go +++ /dev/null @@ -1,27 +0,0 @@ -package nft - -import ( - _ "cosmossdk.io/x/accounts" // import as blank for app wiring - _ "cosmossdk.io/x/bank" // import as blank for app wiring - _ "cosmossdk.io/x/consensus" // import as blank for app wiring - _ "cosmossdk.io/x/mint" // import as blank for app wiring - _ "cosmossdk.io/x/nft/module" // import as blank for app wiring - _ "cosmossdk.io/x/staking" // import as blank for app wiring - - "github.com/cosmos/cosmos-sdk/testutil/configurator" - _ "github.com/cosmos/cosmos-sdk/x/auth" // import as blank for app wiring - _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" // import as blank for app wiring - _ "github.com/cosmos/cosmos-sdk/x/genutil" // import as blank for app wiring -) - -var AppConfig = configurator.NewAppConfig( - configurator.AccountsModule(), - configurator.AuthModule(), - configurator.BankModule(), - configurator.StakingModule(), - configurator.TxModule(), - configurator.ConsensusModule(), - configurator.GenutilModule(), - configurator.MintModule(), - configurator.NFTModule(), -) diff --git a/tests/sims/nft/operations_test.go b/tests/sims/nft/operations_test.go deleted file mode 100644 index 1fc693200c85..000000000000 --- a/tests/sims/nft/operations_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package nft - -import ( - "math/rand" - "testing" - "time" - - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/suite" - - "cosmossdk.io/core/header" - "cosmossdk.io/depinject" - "cosmossdk.io/log" - bankkeeper "cosmossdk.io/x/bank/keeper" - banktestutil "cosmossdk.io/x/bank/testutil" - "cosmossdk.io/x/nft" - nftkeeper "cosmossdk.io/x/nft/keeper" - "cosmossdk.io/x/nft/simulation" - stakingkeeper "cosmossdk.io/x/staking/keeper" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/runtime" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" -) - -type SimTestSuite struct { - suite.Suite - - ctx sdk.Context - - app *runtime.App - codec codec.Codec - interfaceRegistry codectypes.InterfaceRegistry - txConfig client.TxConfig - accountKeeper authkeeper.AccountKeeper - bankKeeper bankkeeper.Keeper - stakingKeeper *stakingkeeper.Keeper - nftKeeper nftkeeper.Keeper -} - -func (suite *SimTestSuite) SetupTest() { - app, err := simtestutil.Setup( - depinject.Configs( - AppConfig, - depinject.Supply(log.NewNopLogger()), - ), - &suite.codec, - &suite.interfaceRegistry, - &suite.txConfig, - &suite.accountKeeper, - &suite.bankKeeper, - &suite.stakingKeeper, - &suite.nftKeeper, - ) - suite.Require().NoError(err) - - suite.app = app - suite.ctx = app.BaseApp.NewContext(false) -} - -func (suite *SimTestSuite) TestWeightedOperations() { - weightedOps := simulation.WeightedOperations( - suite.interfaceRegistry, - make(simtypes.AppParams), - suite.codec, - suite.txConfig, - suite.accountKeeper, - suite.bankKeeper, - suite.nftKeeper, - ) - - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accs := suite.getTestingAccounts(r, 3) - - expected := []struct { - weight int - opMsgRoute string - opMsgName string - }{ - {simulation.WeightSend, nft.ModuleName, simulation.TypeMsgSend}, - } - - for i, w := range weightedOps { - operationMsg, _, err := w.Op()(r, suite.app.BaseApp, suite.ctx, accs, "") - suite.Require().NoError(err) - - // the following checks are very much dependent from the ordering of the output given - // by WeightedOperations. if the ordering in WeightedOperations changes some tests - // will fail - suite.Require().Equal(expected[i].weight, w.Weight(), "weight should be the same") - suite.Require().Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") - suite.Require().Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") - } -} - -func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { - accounts := simtypes.RandomAccounts(r, n) - - initAmt := suite.stakingKeeper.TokensFromConsensusPower(suite.ctx, 200000) - initCoins := sdk.NewCoins(sdk.NewCoin("stake", initAmt)) - - // add coins to the accounts - for _, account := range accounts { - acc := suite.accountKeeper.NewAccountWithAddress(suite.ctx, account.Address) - suite.accountKeeper.SetAccount(suite.ctx, acc) - suite.Require().NoError(banktestutil.FundAccount(suite.ctx, suite.bankKeeper, account.Address, initCoins)) - } - - return accounts -} - -func (suite *SimTestSuite) TestSimulateMsgSend() { - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 2) - blockTime := time.Now().UTC() - ctx := suite.ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - // execute operation - registry := suite.interfaceRegistry - op := simulation.SimulateMsgSend(codec.NewProtoCodec(registry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.nftKeeper) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, ctx, accounts, "") - suite.Require().NoError(err) - - var msg nft.MsgSend - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Len(futureOperations, 0) -} - -func TestSimTestSuite(t *testing.T) { - suite.Run(t, new(SimTestSuite)) -} diff --git a/tests/sims/protocolpool/app_config.go b/tests/sims/protocolpool/app_config.go deleted file mode 100644 index d8b1ace75263..000000000000 --- a/tests/sims/protocolpool/app_config.go +++ /dev/null @@ -1,29 +0,0 @@ -package protocolpool - -import ( - _ "cosmossdk.io/x/accounts" // import as blank for app wiring - _ "cosmossdk.io/x/bank" // import as blank for app wiring - _ "cosmossdk.io/x/consensus" // import as blank for app wiring - _ "cosmossdk.io/x/distribution" // import as blank for app wiring - _ "cosmossdk.io/x/mint" // import as blank for app wiring - _ "cosmossdk.io/x/protocolpool" // import as blank for app wiring - _ "cosmossdk.io/x/staking" // import as blank for app wiring - - "github.com/cosmos/cosmos-sdk/testutil/configurator" - _ "github.com/cosmos/cosmos-sdk/x/auth" // import as blank for app wiring - _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" // import as blank for app wiring - _ "github.com/cosmos/cosmos-sdk/x/genutil" // import as blank for app wiring -) - -var AppConfig = configurator.NewAppConfig( - configurator.AccountsModule(), - configurator.AuthModule(), - configurator.BankModule(), - configurator.StakingModule(), - configurator.TxModule(), - configurator.ConsensusModule(), - configurator.GenutilModule(), - configurator.DistributionModule(), - configurator.MintModule(), - configurator.ProtocolPoolModule(), -) diff --git a/tests/sims/protocolpool/operations_test.go b/tests/sims/protocolpool/operations_test.go deleted file mode 100644 index dffebefd97b6..000000000000 --- a/tests/sims/protocolpool/operations_test.go +++ /dev/null @@ -1,148 +0,0 @@ -package protocolpool - -import ( - "math/rand" - "testing" - - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/require" - - "cosmossdk.io/depinject" - "cosmossdk.io/log" - bankkeeper "cosmossdk.io/x/bank/keeper" - banktestutil "cosmossdk.io/x/bank/testutil" - "cosmossdk.io/x/protocolpool/keeper" - "cosmossdk.io/x/protocolpool/simulation" - "cosmossdk.io/x/protocolpool/types" - stakingkeeper "cosmossdk.io/x/staking/keeper" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/runtime" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" -) - -type suite struct { - Ctx sdk.Context - App *runtime.App - - TxConfig client.TxConfig - Cdc codec.Codec - AccountKeeper authkeeper.AccountKeeper - BankKeeper bankkeeper.Keeper - StakingKeeper *stakingkeeper.Keeper - PoolKeeper keeper.Keeper -} - -func setUpTest(t *testing.T) suite { - t.Helper() - res := suite{} - - var ( - appBuilder *runtime.AppBuilder - err error - ) - - app, err := simtestutil.Setup( - depinject.Configs( - AppConfig, - depinject.Supply(log.NewNopLogger()), - ), - &res.AccountKeeper, - &res.BankKeeper, - &res.Cdc, - &appBuilder, - &res.StakingKeeper, - &res.PoolKeeper, - &res.TxConfig, - ) - require.NoError(t, err) - - res.App = app - res.Ctx = app.BaseApp.NewContext(false) - return res -} - -// TestWeightedOperations tests the weights of the operations. -func TestWeightedOperations(t *testing.T) { - suite := setUpTest(t) - - appParams := make(simtypes.AppParams) - - weightedOps := simulation.WeightedOperations(appParams, suite.Cdc, suite.TxConfig, suite.AccountKeeper, - suite.BankKeeper, suite.PoolKeeper) - - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accs := getTestingAccounts(t, r, suite.AccountKeeper, suite.BankKeeper, suite.StakingKeeper, suite.Ctx, 3) - - expected := []struct { - weight int - opMsgRoute string - opMsgName string - }{ - {simulation.DefaultWeightMsgFundCommunityPool, types.ModuleName, sdk.MsgTypeURL(&types.MsgFundCommunityPool{})}, - } - - for i, w := range weightedOps { - operationMsg, _, err := w.Op()(r, suite.App.BaseApp, suite.Ctx, accs, "") - require.NoError(t, err) - - // the following checks are very much dependent from the ordering of the output given - // by WeightedOperations. if the ordering in WeightedOperations changes some tests - // will fail - require.Equal(t, expected[i].weight, w.Weight(), "weight should be the same") - require.Equal(t, expected[i].opMsgRoute, operationMsg.Route, "route should be the same") - require.Equal(t, expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") - } -} - -// TestSimulateMsgFundCommunityPool tests the normal scenario of a valid message of type TypeMsgFundCommunityPool. -// Abonormal scenarios, where the message is created by an errors, are not tested here. -func TestSimulateMsgFundCommunityPool(t *testing.T) { - suite := setUpTest(t) - - // setup 3 accounts - s := rand.NewSource(1) - r := rand.New(s) - accounts := getTestingAccounts(t, r, suite.AccountKeeper, suite.BankKeeper, suite.StakingKeeper, suite.Ctx, 3) - - // execute operation - op := simulation.SimulateMsgFundCommunityPool(suite.TxConfig, suite.AccountKeeper, suite.BankKeeper, suite.PoolKeeper) - operationMsg, futureOperations, err := op(r, suite.App.BaseApp, suite.Ctx, accounts, "") - require.NoError(t, err) - - var msg types.MsgFundCommunityPool - err = proto.Unmarshal(operationMsg.Msg, &msg) - require.NoError(t, err) - require.True(t, operationMsg.OK) - require.Equal(t, "4896096stake", msg.Amount.String()) - require.Equal(t, "cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Depositor) - require.Equal(t, sdk.MsgTypeURL(&types.MsgFundCommunityPool{}), sdk.MsgTypeURL(&msg)) - require.Len(t, futureOperations, 0) -} - -func getTestingAccounts( - t *testing.T, r *rand.Rand, - accountKeeper authkeeper.AccountKeeper, bankKeeper bankkeeper.Keeper, - stakingKeeper *stakingkeeper.Keeper, ctx sdk.Context, n int, -) []simtypes.Account { - t.Helper() - accounts := simtypes.RandomAccounts(r, n) - - initAmt := stakingKeeper.TokensFromConsensusPower(ctx, 200) - initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) - - // add coins to the accounts - for _, account := range accounts { - acc := accountKeeper.NewAccountWithAddress(ctx, account.Address) - accountKeeper.SetAccount(ctx, acc) - require.NoError(t, banktestutil.FundAccount(ctx, bankKeeper, account.Address, initCoins)) - } - - return accounts -} diff --git a/tests/sims/slashing/app_config.go b/tests/sims/slashing/app_config.go deleted file mode 100644 index f649e196fb75..000000000000 --- a/tests/sims/slashing/app_config.go +++ /dev/null @@ -1,31 +0,0 @@ -package slashing - -import ( - _ "cosmossdk.io/x/accounts" // import as blank for app wiring - _ "cosmossdk.io/x/bank" // import as blank for app wiring - _ "cosmossdk.io/x/consensus" // import as blank for app wiring - _ "cosmossdk.io/x/distribution" // import as blank for app wiring - _ "cosmossdk.io/x/mint" // import as blank for app wiring - _ "cosmossdk.io/x/protocolpool" // import as blank for app wiring - _ "cosmossdk.io/x/slashing" // import as blank for app wiring - _ "cosmossdk.io/x/staking" // import as blank for app wiring - - "github.com/cosmos/cosmos-sdk/testutil/configurator" - _ "github.com/cosmos/cosmos-sdk/x/auth" // import as blank for app wiring - _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" // import as blank for app wiring - _ "github.com/cosmos/cosmos-sdk/x/genutil" // import as blank for app wiring -) - -var AppConfig = configurator.NewAppConfig( - configurator.AccountsModule(), - configurator.AuthModule(), - configurator.BankModule(), - configurator.StakingModule(), - configurator.SlashingModule(), - configurator.TxModule(), - configurator.ConsensusModule(), - configurator.GenutilModule(), - configurator.MintModule(), - configurator.DistributionModule(), - configurator.ProtocolPoolModule(), -) diff --git a/tests/sims/slashing/operations_test.go b/tests/sims/slashing/operations_test.go deleted file mode 100644 index f4b6c31773bd..000000000000 --- a/tests/sims/slashing/operations_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package slashing - -import ( - "fmt" - "math/rand" - "testing" - "time" - - abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" - cmttypes "github.com/cometbft/cometbft/types" - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/suite" - - "cosmossdk.io/collections" - "cosmossdk.io/core/header" - "cosmossdk.io/depinject" - "cosmossdk.io/log" - "cosmossdk.io/math" - bankkeeper "cosmossdk.io/x/bank/keeper" - banktestutil "cosmossdk.io/x/bank/testutil" - distributionkeeper "cosmossdk.io/x/distribution/keeper" - distrtypes "cosmossdk.io/x/distribution/types" - mintkeeper "cosmossdk.io/x/mint/keeper" - minttypes "cosmossdk.io/x/mint/types" - slashingkeeper "cosmossdk.io/x/slashing/keeper" - "cosmossdk.io/x/slashing/simulation" - "cosmossdk.io/x/slashing/types" - stakingkeeper "cosmossdk.io/x/staking/keeper" - stakingtypes "cosmossdk.io/x/staking/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - "github.com/cosmos/cosmos-sdk/runtime" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" -) - -type SimTestSuite struct { - suite.Suite - - ctx sdk.Context - r *rand.Rand - accounts []simtypes.Account - - app *runtime.App - codec codec.Codec - interfaceRegistry codectypes.InterfaceRegistry - txConfig client.TxConfig - accountKeeper authkeeper.AccountKeeper - bankKeeper bankkeeper.Keeper - stakingKeeper *stakingkeeper.Keeper - slashingKeeper slashingkeeper.Keeper - distrKeeper distributionkeeper.Keeper - mintKeeper mintkeeper.Keeper -} - -func (suite *SimTestSuite) SetupTest() { - s := rand.NewSource(1) - suite.r = rand.New(s) - accounts := simtypes.RandomAccounts(suite.r, 4) - - // create validator (non random as using a seed) - createValidator := func() (*cmttypes.ValidatorSet, error) { - account := accounts[0] - cmtPk, err := cryptocodec.ToCmtPubKeyInterface(account.ConsKey.PubKey()) - if err != nil { - return nil, fmt.Errorf("failed to create pubkey: %w", err) - } - - validator := cmttypes.NewValidator(cmtPk, 1) - - return cmttypes.NewValidatorSet([]*cmttypes.Validator{validator}), nil - } - - startupCfg := simtestutil.DefaultStartUpConfig() - startupCfg.ValidatorSet = createValidator - - app, err := simtestutil.SetupWithConfiguration( - depinject.Configs( - AppConfig, - depinject.Supply(log.NewNopLogger()), - ), - startupCfg, - &suite.codec, - &suite.interfaceRegistry, - &suite.txConfig, - &suite.accountKeeper, - &suite.bankKeeper, - &suite.stakingKeeper, - &suite.mintKeeper, - &suite.slashingKeeper, - &suite.distrKeeper, - ) - - suite.Require().NoError(err) - suite.app = app - suite.ctx = app.BaseApp.NewContext(false) - - // remove genesis validator account - suite.accounts = accounts[1:] - - initAmt := suite.stakingKeeper.TokensFromConsensusPower(suite.ctx, 200) - initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) - - // add coins to the accounts - for _, account := range suite.accounts { - acc := suite.accountKeeper.NewAccountWithAddress(suite.ctx, account.Address) - suite.accountKeeper.SetAccount(suite.ctx, acc) - suite.Require().NoError(banktestutil.FundAccount(suite.ctx, suite.bankKeeper, account.Address, initCoins)) - } - suite.Require().NoError(suite.mintKeeper.Params.Set(suite.ctx, minttypes.DefaultParams())) - suite.Require().NoError(suite.mintKeeper.Minter.Set(suite.ctx, minttypes.DefaultInitialMinter())) -} - -func TestSimTestSuite(t *testing.T) { - suite.Run(t, new(SimTestSuite)) -} - -// TestWeightedOperations tests the weights of the operations. -func (suite *SimTestSuite) TestWeightedOperations() { - ctx := suite.ctx.WithChainID("test-chain") - appParams := make(simtypes.AppParams) - - expected := []struct { - weight int - opMsgRoute string - opMsgName string - }{ - {simulation.DefaultWeightMsgUnjail, types.ModuleName, sdk.MsgTypeURL(&types.MsgUnjail{})}, - } - - weightedOps := simulation.WeightedOperations(suite.interfaceRegistry, appParams, suite.codec, suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.slashingKeeper, suite.stakingKeeper) - for i, w := range weightedOps { - operationMsg, _, err := w.Op()(suite.r, suite.app.BaseApp, ctx, suite.accounts, ctx.ChainID()) - suite.Require().NoError(err) - - // the following checks are very much dependent from the ordering of the output given - // by WeightedOperations. if the ordering in WeightedOperations changes some tests - // will fail - suite.Require().Equal(expected[i].weight, w.Weight(), "weight should be the same") - suite.Require().Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") - suite.Require().Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") - } -} - -// TestSimulateMsgUnjail tests the normal scenario of a valid message of type types.MsgUnjail. -// Abonormal scenarios, where the message is created by an errors, are not tested here. -func (suite *SimTestSuite) TestSimulateMsgUnjail() { - blockTime := time.Now().UTC() - ctx := suite.ctx.WithHeaderInfo(header.Info{Time: blockTime}) - - // setup accounts[0] as validator0 - validator0, err := getTestingValidator0(ctx, suite.stakingKeeper, suite.accounts) - suite.Require().NoError(err) - - // setup validator0 by consensus address - err = suite.stakingKeeper.SetValidatorByConsAddr(ctx, validator0) - suite.Require().NoError(err) - - val0ConsAddress, err := validator0.GetConsAddr() - suite.Require().NoError(err) - val0ConsAddressStr, err := suite.stakingKeeper.ConsensusAddressCodec().BytesToString(val0ConsAddress) - suite.Require().NoError(err) - info := types.NewValidatorSigningInfo(val0ConsAddressStr, int64(4), - time.Unix(2, 0), false, int64(10)) - err = suite.slashingKeeper.ValidatorSigningInfo.Set(ctx, val0ConsAddress, info) - suite.Require().NoError(err) - // put validator0 in jail - suite.Require().NoError(suite.stakingKeeper.Jail(ctx, val0ConsAddress)) - - // setup self delegation - delTokens := suite.stakingKeeper.TokensFromConsensusPower(ctx, 2) - validator0, issuedShares := validator0.AddTokensFromDel(delTokens) - val0AccAddress, err := sdk.ValAddressFromBech32(validator0.OperatorAddress) - suite.Require().NoError(err) - selfDelegation := stakingtypes.NewDelegation(suite.accounts[0].Address.String(), validator0.GetOperator(), issuedShares) - suite.Require().NoError(suite.stakingKeeper.SetDelegation(ctx, selfDelegation)) - suite.Require().NoError(suite.distrKeeper.DelegatorStartingInfo.Set(ctx, collections.Join(val0AccAddress, sdk.AccAddress(val0AccAddress)), distrtypes.NewDelegatorStartingInfo(2, math.LegacyOneDec(), 200))) - - // begin a new block - _, err = suite.app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: suite.app.LastBlockHeight() + 1, Hash: suite.app.LastCommitID().Hash, Time: blockTime}) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgUnjail(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.slashingKeeper, suite.stakingKeeper) - operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, ctx, suite.accounts, "") - suite.Require().NoError(err) - - var msg types.MsgUnjail - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal("cosmosvaloper1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7epjs3u", msg.ValidatorAddr) - suite.Require().Len(futureOperations, 0) -} - -func getTestingValidator0(ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper, accounts []simtypes.Account) (stakingtypes.Validator, error) { - commission0 := stakingtypes.NewCommission(math.LegacyZeroDec(), math.LegacyOneDec(), math.LegacyOneDec()) - return getTestingValidator(ctx, stakingKeeper, accounts, commission0, 0) -} - -func getTestingValidator(ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper, accounts []simtypes.Account, commission stakingtypes.Commission, n int) (stakingtypes.Validator, error) { - account := accounts[n] - valPubKey := account.ConsKey.PubKey() - valAddr := sdk.ValAddress(account.PubKey.Address().Bytes()) - validator, err := stakingtypes.NewValidator(valAddr.String(), valPubKey, stakingtypes.Description{}) - if err != nil { - return stakingtypes.Validator{}, fmt.Errorf("failed to create validator: %w", err) - } - - validator, err = validator.SetInitialCommission(commission) - if err != nil { - return stakingtypes.Validator{}, fmt.Errorf("failed to set initial commission: %w", err) - } - - validator.DelegatorShares = math.LegacyNewDec(100) - validator.Tokens = math.NewInt(1000000) - - err = stakingKeeper.SetValidator(ctx, validator) - if err != nil { - return stakingtypes.Validator{}, err - } - return validator, nil -} diff --git a/testutil/sims/simulation_helpers.go b/testutil/sims/simulation_helpers.go index a513a783edcf..c3ac6b4c0895 100644 --- a/testutil/sims/simulation_helpers.go +++ b/testutil/sims/simulation_helpers.go @@ -7,79 +7,14 @@ import ( "os" "sync" - dbm "github.com/cosmos/cosmos-db" - corestore "cosmossdk.io/core/store" - "cosmossdk.io/log" storetypes "cosmossdk.io/store/types" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/runtime" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/kv" - "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) -// SetupSimulation creates the config, db (levelDB), temporary directory and logger for the simulation tests. -// If `skip` is false it skips the current test. `skip` should be set using the `FlagEnabledValue` flag. -// Returns error on an invalid db instantiation or temp dir creation. -func SetupSimulation(config simtypes.Config, dirPrefix, dbName string, verbose, skip bool) (corestore.KVStoreWithBatch, string, log.Logger, bool, error) { - if !skip { - return nil, "", nil, true, nil - } - - var logger log.Logger - if verbose { - logger = log.NewLogger(os.Stdout) - } else { - logger = log.NewNopLogger() - } - - dir, err := os.MkdirTemp("", dirPrefix) - if err != nil { - return nil, "", nil, false, err - } - - db, err := dbm.NewDB(dbName, dbm.BackendType(config.DBBackend), dir) - if err != nil { - return nil, "", nil, false, err - } - - return db, dir, logger, false, nil -} - -// SimulationOperations retrieves the simulation params from the provided file path -// and returns all the modules weighted operations -func SimulationOperations(app runtime.AppSimI, cdc codec.Codec, config simtypes.Config, txConfig client.TxConfig) []simtypes.WeightedOperation { - signingCtx := cdc.InterfaceRegistry().SigningContext() - simState := module.SimulationState{ - AppParams: make(simtypes.AppParams), - Cdc: cdc, - AddressCodec: signingCtx.AddressCodec(), - ValidatorCodec: signingCtx.ValidatorAddressCodec(), - TxConfig: txConfig, - BondDenom: sdk.DefaultBondDenom, - } - - if config.ParamsFile != "" { - bz, err := os.ReadFile(config.ParamsFile) - if err != nil { - panic(err) - } - - err = json.Unmarshal(bz, &simState.AppParams) - if err != nil { - panic(err) - } - } - - simState.LegacyProposalContents = app.SimulationManager().GetProposalContents(simState) //nolint:staticcheck // we're testing the old way here - simState.ProposalMsgs = app.SimulationManager().GetProposalMsgs(simState) - return app.SimulationManager().WeightedOperations(simState) -} - // CheckExportSimulation exports the app state and simulation parameters to JSON // if the export paths are defined. func CheckExportSimulation(app runtime.AppSimI, config simtypes.Config, params simtypes.Params) error { @@ -115,10 +50,10 @@ type DBStatsInterface interface { } // PrintStats prints the corresponding statistics from the app DB. -func PrintStats(db DBStatsInterface) { - fmt.Println("\nLevelDB Stats") - fmt.Println(db.Stats()["leveldb.stats"]) - fmt.Println("LevelDB cached block size", db.Stats()["leveldb.cachedblock"]) +func PrintStats(db DBStatsInterface, logLine func(args ...any)) { + logLine("\nLevelDB Stats") + logLine(db.Stats()["leveldb.stats"]) + logLine("LevelDB cached block size", db.Stats()["leveldb.cachedblock"]) } // GetSimulationLog unmarshals the KVPair's Value to the corresponding type based on the diff --git a/testutil/sims/state_helpers.go b/testutil/sims/state_helpers.go index 684ef474d724..76d06b8959d6 100644 --- a/testutil/sims/state_helpers.go +++ b/testutil/sims/state_helpers.go @@ -2,6 +2,7 @@ package sims import ( "bufio" + "bytes" "encoding/json" "errors" "io" @@ -36,41 +37,11 @@ const ( ) // AppStateFn returns the initial application state using a genesis or the simulation parameters. -// It calls appStateFnWithExtendedCb with nil rawStateCb. func AppStateFn( - cdc codec.JSONCodec, - addresCodec, validatorCodec address.Codec, - simManager *module.SimulationManager, - genesisState map[string]json.RawMessage, -) simtypes.AppStateFn { - return appStateFnWithExtendedCb(cdc, addresCodec, validatorCodec, simManager, genesisState, nil) -} - -// appStateFnWithExtendedCb returns the initial application state using a genesis or the simulation parameters. -// It calls appStateFnWithExtendedCbs with nil moduleStateCb. -func appStateFnWithExtendedCb( - cdc codec.JSONCodec, - addresCodec, validatorCodec address.Codec, - simManager *module.SimulationManager, - genesisState map[string]json.RawMessage, - rawStateCb func(rawState map[string]json.RawMessage), -) simtypes.AppStateFn { - return appStateFnWithExtendedCbs(cdc, addresCodec, validatorCodec, simManager, genesisState, nil, rawStateCb) -} - -// appStateFnWithExtendedCbs returns the initial application state using a genesis or the simulation parameters. -// It panics if the user provides files for both of them. -// If a file is not given for the genesis or the sim params, it creates a randomized one. -// genesisState is the default genesis state of the whole app. -// moduleStateCb is the callback function to access moduleState. -// rawStateCb is the callback function to extend rawState. -func appStateFnWithExtendedCbs( cdc codec.JSONCodec, addressCodec, validatorCodec address.Codec, - simManager *module.SimulationManager, + modules []module.AppModuleSimulation, genesisState map[string]json.RawMessage, - moduleStateCb func(moduleName string, genesisState interface{}), - rawStateCb func(rawState map[string]json.RawMessage), ) simtypes.AppStateFn { return func( r *rand.Rand, @@ -111,11 +82,11 @@ func appStateFnWithExtendedCbs( if err != nil { panic(err) } - appState, simAccs = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams, genesisState, addressCodec, validatorCodec) + appState, simAccs = AppStateRandomizedFn(modules, r, cdc, accs, genesisTimestamp, appParams, genesisState, addressCodec, validatorCodec) default: appParams := make(simtypes.AppParams) - appState, simAccs = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams, genesisState, addressCodec, validatorCodec) + appState, simAccs = AppStateRandomizedFn(modules, r, cdc, accs, genesisTimestamp, appParams, genesisState, addressCodec, validatorCodec) } rawState := make(map[string]json.RawMessage) @@ -172,17 +143,9 @@ func appStateFnWithExtendedCbs( stakingtypes.ModuleName: stakingState, testutil.BankModuleName: bankState, } { - if moduleStateCb != nil { - moduleStateCb(name, state) - } rawState[name] = cdc.MustMarshalJSON(state) } - // extend state from callback function - if rawStateCb != nil { - rawStateCb(rawState) - } - // replace appstate appState, err = json.Marshal(rawState) if err != nil { @@ -195,7 +158,7 @@ func appStateFnWithExtendedCbs( // AppStateRandomizedFn creates calls each module's GenesisState generator function // and creates the simulation params func AppStateRandomizedFn( - simManager *module.SimulationManager, + modules []module.AppModuleSimulation, r *rand.Rand, cdc codec.JSONCodec, accs []simtypes.Account, @@ -237,8 +200,7 @@ func AppStateRandomizedFn( BondDenom: sdk.DefaultBondDenom, GenTimestamp: genesisTimestamp, } - - simManager.GenerateGenesisStates(simState) + generateGenesisStates(modules, simState) appState, err := json.Marshal(genesisState) if err != nil { @@ -250,31 +212,38 @@ func AppStateRandomizedFn( // AppStateFromGenesisFileFn util function to generate the genesis AppState // from a genesis.json file. -func AppStateFromGenesisFileFn(r io.Reader, cdc codec.JSONCodec, genesisFile string) (genutiltypes.AppGenesis, []simtypes.Account, error) { +func AppStateFromGenesisFileFn(_ io.Reader, cdc codec.JSONCodec, genesisFile string) (genutiltypes.AppGenesis, []simtypes.Account, error) { file, err := os.Open(filepath.Clean(genesisFile)) if err != nil { panic(err) } + defer file.Close() genesis, err := genutiltypes.AppGenesisFromReader(bufio.NewReader(file)) if err != nil { - return *genesis, nil, err + return genutiltypes.AppGenesis{}, nil, err } - if err := file.Close(); err != nil { - return *genesis, nil, err + appStateJSON := genesis.AppState + newAccs, err := AccountsFromAppState(cdc, appStateJSON) + if err != nil { + panic(err) } + return *genesis, newAccs, nil +} + +func AccountsFromAppState(cdc codec.JSONCodec, appStateJSON json.RawMessage) ([]simtypes.Account, error) { var appState map[string]json.RawMessage - if err = json.Unmarshal(genesis.AppState, &appState); err != nil { - return *genesis, nil, err + if err := json.Unmarshal(appStateJSON, &appState); err != nil { + return nil, err } var authGenesis authtypes.GenesisState if appState[testutil.AuthModuleName] != nil { cdc.MustUnmarshalJSON(appState[testutil.AuthModuleName], &authGenesis) } - + r := bufio.NewReader(bytes.NewReader(appStateJSON)) // any deterministic source newAccs := make([]simtypes.Account, len(authGenesis.Accounts)) for i, acc := range authGenesis.Accounts { // Pick a random private key, since we don't know the actual key @@ -282,20 +251,25 @@ func AppStateFromGenesisFileFn(r io.Reader, cdc codec.JSONCodec, genesisFile str // and these keys are never actually used to sign by mock CometBFT. privkeySeed := make([]byte, 15) if _, err := r.Read(privkeySeed); err != nil { - panic(err) + return nil, err } privKey := secp256k1.GenPrivKeyFromSecret(privkeySeed) a, ok := acc.GetCachedValue().(sdk.AccountI) if !ok { - return *genesis, nil, errors.New("expected account") + return nil, errors.New("expected account") } // create simulator accounts simAcc := simtypes.Account{PrivKey: privKey, PubKey: privKey.PubKey(), Address: a.GetAddress(), ConsKey: ed25519.GenPrivKeyFromSecret(privkeySeed)} newAccs[i] = simAcc } + return newAccs, nil +} - return *genesis, newAccs, nil +func generateGenesisStates(modules []module.AppModuleSimulation, simState *module.SimulationState) { + for _, m := range modules { + m.GenerateGenesisState(simState) + } } diff --git a/testutils/sims/runner.go b/testutils/sims/runner.go deleted file mode 100644 index e30a252c0d29..000000000000 --- a/testutils/sims/runner.go +++ /dev/null @@ -1,251 +0,0 @@ -package sims - -import ( - "fmt" - "io" - "os" - "path/filepath" - "testing" - - dbm "github.com/cosmos/cosmos-db" - "github.com/stretchr/testify/require" - - corestore "cosmossdk.io/core/store" - "cosmossdk.io/log" - - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/runtime" - "github.com/cosmos/cosmos-sdk/server" - servertypes "github.com/cosmos/cosmos-sdk/server/types" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" -) - -const SimAppChainID = "simulation-app" - -// this list of seeds was imported from the original simulation runner: https://github.com/cosmos/tools/blob/v1.0.0/cmd/runsim/main.go#L32 -var defaultSeeds = []int64{ - 1, 2, 4, 7, - 32, 123, 124, 582, 1893, 2989, - 3012, 4728, 37827, 981928, 87821, 891823782, - 989182, 89182391, 11, 22, 44, 77, 99, 2020, - 3232, 123123, 124124, 582582, 18931893, - 29892989, 30123012, 47284728, 7601778, 8090485, - 977367484, 491163361, 424254581, 673398983, -} - -type SimStateFactory struct { - Codec codec.Codec - AppStateFn simtypes.AppStateFn - BlockedAddr map[string]bool -} - -// SimulationApp abstract app that is used by sims -type SimulationApp interface { - runtime.AppSimI - SetNotSigverifyTx() - GetBaseApp() *baseapp.BaseApp - TxConfig() client.TxConfig - Close() error -} - -// Run is a helper function that runs a simulation test with the given parameters. -// It calls the RunWithSeeds function with the default seeds and parameters. -// -// This is the entrypoint to run simulation tests that used to run with the runsim binary. -func Run[T SimulationApp]( - t *testing.T, - appFactory func( - logger log.Logger, - db corestore.KVStoreWithBatch, - traceStore io.Writer, - loadLatest bool, - appOpts servertypes.AppOptions, - baseAppOptions ...func(*baseapp.BaseApp), - ) T, - setupStateFactory func(app T) SimStateFactory, - postRunActions ...func(t *testing.T, app TestInstance[T]), -) { - t.Helper() - RunWithSeeds(t, appFactory, setupStateFactory, defaultSeeds, nil, postRunActions...) -} - -// RunWithSeeds is a helper function that runs a simulation test with the given parameters. -// It iterates over the provided seeds and runs the simulation test for each seed in parallel. -// -// It sets up the environment, creates an instance of the simulation app, -// calls the simulation.SimulateFromSeed function to run the simulation, and performs post-run actions for each seed. -// The execution is deterministic and can be used for fuzz tests as well. -// -// The system under test is isolated for each run but unlike the old runsim command, there is no Process separation. -// This means, global caches may be reused for example. This implementation build upon the vanialla Go stdlib test framework. -func RunWithSeeds[T SimulationApp]( - t *testing.T, - appFactory func( - logger log.Logger, - db corestore.KVStoreWithBatch, - traceStore io.Writer, - loadLatest bool, - appOpts servertypes.AppOptions, - baseAppOptions ...func(*baseapp.BaseApp), - ) T, - setupStateFactory func(app T) SimStateFactory, - seeds []int64, - fuzzSeed []byte, - postRunActions ...func(t *testing.T, app TestInstance[T]), -) { - t.Helper() - cfg := cli.NewConfigFromFlags() - cfg.ChainID = SimAppChainID - for i := range seeds { - seed := seeds[i] - t.Run(fmt.Sprintf("seed: %d", seed), func(t *testing.T) { - t.Parallel() - // setup environment - tCfg := cfg.With(t, seed, fuzzSeed) - testInstance := NewSimulationAppInstance(t, tCfg, appFactory) - var runLogger log.Logger - if cli.FlagVerboseValue { - runLogger = log.NewTestLogger(t) - } else { - runLogger = log.NewTestLoggerInfo(t) - } - runLogger = runLogger.With("seed", tCfg.Seed) - app := testInstance.App - stateFactory := setupStateFactory(app) - simParams, err := simulation.SimulateFromSeedX( - t, - runLogger, - WriteToDebugLog(runLogger), - app.GetBaseApp(), - stateFactory.AppStateFn, - simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 - simtestutil.SimulationOperations(app, stateFactory.Codec, tCfg, app.TxConfig()), - stateFactory.BlockedAddr, - tCfg, - stateFactory.Codec, - app.TxConfig().SigningContext().AddressCodec(), - testInstance.ExecLogWriter, - ) - require.NoError(t, err) - err = simtestutil.CheckExportSimulation(app, tCfg, simParams) - require.NoError(t, err) - if tCfg.Commit { - db, ok := testInstance.DB.(simtestutil.DBStatsInterface) - if ok { - simtestutil.PrintStats(db) - } - } - for _, step := range postRunActions { - step(t, testInstance) - } - require.NoError(t, app.Close()) - }) - } -} - -// TestInstance is a generic type that represents an instance of a SimulationApp used for testing simulations. -// It contains the following fields: -// - App: The instance of the SimulationApp under test. -// - DB: The LevelDB database for the simulation app. -// - WorkDir: The temporary working directory for the simulation app. -// - Cfg: The configuration flags for the simulator. -// - AppLogger: The logger used for logging in the app during the simulation, with seed value attached. -// - ExecLogWriter: Captures block and operation data coming from the simulation -type TestInstance[T SimulationApp] struct { - App T - DB corestore.KVStoreWithBatch - WorkDir string - Cfg simtypes.Config - AppLogger log.Logger - ExecLogWriter simulation.LogWriter -} - -// NewSimulationAppInstance initializes and returns a TestInstance of a SimulationApp. -// The function takes a testing.T instance, a simtypes.Config instance, and an appFactory function as parameters. -// It creates a temporary working directory and a LevelDB database for the simulation app. -// The function then initializes a logger based on the verbosity flag and sets the logger's seed to the test configuration's seed. -// The database is closed and cleaned up on test completion. -func NewSimulationAppInstance[T SimulationApp]( - t *testing.T, - tCfg simtypes.Config, - appFactory func(logger log.Logger, db corestore.KVStoreWithBatch, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp)) T, -) TestInstance[T] { - t.Helper() - workDir := t.TempDir() - require.NoError(t, os.Mkdir(filepath.Join(workDir, "data"), 0o755)) - - dbDir := filepath.Join(workDir, "leveldb-app-sim") - var logger log.Logger - if cli.FlagVerboseValue { - logger = log.NewTestLogger(t) - } else { - logger = log.NewTestLoggerError(t) - } - logger = logger.With("seed", tCfg.Seed) - - db, err := dbm.NewDB("Simulation", dbm.BackendType(tCfg.DBBackend), dbDir) - require.NoError(t, err) - t.Cleanup(func() { - _ = db.Close() // ensure db is closed - }) - appOptions := make(simtestutil.AppOptionsMap) - appOptions[flags.FlagHome] = workDir - appOptions[server.FlagInvCheckPeriod] = cli.FlagPeriodValue - - app := appFactory(logger, db, nil, true, appOptions, baseapp.SetChainID(SimAppChainID)) - if !cli.FlagSigverifyTxValue { - app.SetNotSigverifyTx() - } - return TestInstance[T]{ - App: app, - DB: db, - WorkDir: workDir, - Cfg: tCfg, - AppLogger: logger, - ExecLogWriter: &simulation.StandardLogWriter{Seed: tCfg.Seed}, - } -} - -var _ io.Writer = writerFn(nil) - -type writerFn func(p []byte) (n int, err error) - -func (w writerFn) Write(p []byte) (n int, err error) { - return w(p) -} - -// WriteToDebugLog is an adapter to io.Writer interface -func WriteToDebugLog(logger log.Logger) io.Writer { - return writerFn(func(p []byte) (n int, err error) { - logger.Debug(string(p)) - return len(p), nil - }) -} - -// AppOptionsFn is an adapter to the single method AppOptions interface -type AppOptionsFn func(string) any - -func (f AppOptionsFn) Get(k string) any { - return f(k) -} - -func (f AppOptionsFn) GetString(k string) string { - str, ok := f(k).(string) - if !ok { - return "" - } - - return str -} - -// FauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of -// an IAVLStore for faster simulation speed. -func FauxMerkleModeOpt(bapp *baseapp.BaseApp) { - bapp.SetFauxMerkleMode() -} diff --git a/types/module/simulation.go b/types/module/simulation.go index 20bb46ba1976..57e31171b5bd 100644 --- a/types/module/simulation.go +++ b/types/module/simulation.go @@ -18,27 +18,31 @@ import ( // AppModuleSimulation defines the standard functions that every module should expose // for the SDK blockchain simulator type AppModuleSimulation interface { - // randomized genesis states + // GenerateGenesisState randomized genesis states GenerateGenesisState(input *SimulationState) - // register a func to decode the each module's defined types from their corresponding store key + // RegisterStoreDecoder register a func to decode the each module's defined types from their corresponding store key RegisterStoreDecoder(simulation.StoreDecoderRegistry) - - // simulation operations (i.e msgs) with their respective weight - WeightedOperations(simState SimulationState) []simulation.WeightedOperation -} - -// HasProposalMsgs defines the messages that can be used to simulate governance (v1) proposals -type HasProposalMsgs interface { - // msg functions used to simulate governance proposals - ProposalMsgs(simState SimulationState) []simulation.WeightedProposalMsg } +type ( + HasLegacyWeightedOperations interface { + // WeightedOperations simulation operations (i.e msgs) with their respective weight + WeightedOperations(simState SimulationState) []simulation.WeightedOperation + } + // HasLegacyProposalMsgs defines the messages that can be used to simulate governance (v1) proposals + // Deprecated replaced by HasProposalMsgsX + HasLegacyProposalMsgs interface { + // ProposalMsgs msg functions used to simulate governance proposals + ProposalMsgs(simState SimulationState) []simulation.WeightedProposalMsg + } -// HasProposalContents defines the contents that can be used to simulate legacy governance (v1beta1) proposals -type HasProposalContents interface { - // content functions used to simulate governance proposals - ProposalContents(simState SimulationState) []simulation.WeightedProposalContent //nolint:staticcheck // legacy v1beta1 governance -} + // HasLegacyProposalContents defines the contents that can be used to simulate legacy governance (v1beta1) proposals + // Deprecated replaced by HasProposalMsgsX + HasLegacyProposalContents interface { + // ProposalContents content functions used to simulate governance proposals + ProposalContents(simState SimulationState) []simulation.WeightedProposalContent //nolint:staticcheck // legacy v1beta1 governance + } +) // SimulationManager defines a simulation manager that provides the high level utility // for managing and executing simulation functionalities for a group of modules @@ -64,14 +68,13 @@ func NewSimulationManager(modules ...AppModuleSimulation) *SimulationManager { // Then it attempts to cast every provided AppModule into an AppModuleSimulation. // If the cast succeeds, its included, otherwise it is excluded. func NewSimulationManagerFromAppModules(modules map[string]appmodule.AppModule, overrideModules map[string]AppModuleSimulation) *SimulationManager { - simModules := []AppModuleSimulation{} appModuleNamesSorted := make([]string, 0, len(modules)) for moduleName := range modules { appModuleNamesSorted = append(appModuleNamesSorted, moduleName) } - sort.Strings(appModuleNamesSorted) + var simModules []AppModuleSimulation for _, moduleName := range appModuleNamesSorted { // for every module, see if we override it. If so, use override. // Else, if we can cast the app module into a simulation module add it. @@ -95,7 +98,7 @@ func NewSimulationManagerFromAppModules(modules map[string]appmodule.AppModule, func (sm *SimulationManager) GetProposalContents(simState SimulationState) []simulation.WeightedProposalContent { wContents := make([]simulation.WeightedProposalContent, 0, len(sm.Modules)) for _, module := range sm.Modules { - if module, ok := module.(HasProposalContents); ok { + if module, ok := module.(HasLegacyProposalContents); ok { wContents = append(wContents, module.ProposalContents(simState)...) } } @@ -103,19 +106,6 @@ func (sm *SimulationManager) GetProposalContents(simState SimulationState) []sim return wContents } -// GetProposalMsgs returns each module's proposal msg generator function -// with their default operation weight and key. -func (sm *SimulationManager) GetProposalMsgs(simState SimulationState) []simulation.WeightedProposalMsg { - wContents := make([]simulation.WeightedProposalMsg, 0, len(sm.Modules)) - for _, module := range sm.Modules { - if module, ok := module.(HasProposalMsgs); ok { - wContents = append(wContents, module.ProposalMsgs(simState)...) - } - } - - return wContents -} - // RegisterStoreDecoders registers each of the modules' store decoders into a map func (sm *SimulationManager) RegisterStoreDecoders() { for _, module := range sm.Modules { @@ -131,16 +121,6 @@ func (sm *SimulationManager) GenerateGenesisStates(simState *SimulationState) { } } -// WeightedOperations returns all the modules' weighted operations of an application -func (sm *SimulationManager) WeightedOperations(simState SimulationState) []simulation.WeightedOperation { - wOps := make([]simulation.WeightedOperation, 0, len(sm.Modules)) - for _, module := range sm.Modules { - wOps = append(wOps, module.WeightedOperations(simState)...) - } - - return wOps -} - // SimulationState is the input parameters used on each of the module's randomized // GenesisState generator function type SimulationState struct { @@ -158,7 +138,4 @@ type SimulationState struct { GenTimestamp time.Time // genesis timestamp UnbondTime time.Duration // staking unbond time stored to use it as the slashing maximum evidence duration LegacyParamChange []simulation.LegacyParamChange // simulated parameter changes from modules - //nolint:staticcheck // legacy used for testing - LegacyProposalContents []simulation.WeightedProposalContent // proposal content generator functions with their default weight and app sim key - ProposalMsgs []simulation.WeightedProposalMsg // proposal msg generator functions with their default weight and app sim key } diff --git a/types/simulation/account.go b/types/simulation/account.go index d046ea23e284..0bc7c79fc1f3 100644 --- a/types/simulation/account.go +++ b/types/simulation/account.go @@ -14,10 +14,11 @@ import ( // eventually more useful data can be placed in here. // (e.g. number of coins) type Account struct { - PrivKey cryptotypes.PrivKey - PubKey cryptotypes.PubKey - Address sdk.AccAddress - ConsKey cryptotypes.PrivKey + PrivKey cryptotypes.PrivKey + PubKey cryptotypes.PubKey + Address sdk.AccAddress + ConsKey cryptotypes.PrivKey + AddressBech32 string } // Equals returns true if two accounts are equal @@ -50,10 +51,11 @@ func RandomAccounts(r *rand.Rand, n int) []Account { } idx[string(addr.Bytes())] = struct{}{} accs[i] = Account{ - Address: addr, - PrivKey: privKey, - PubKey: pubKey, - ConsKey: ed25519.GenPrivKeyFromSecret(privkeySeed), + Address: addr, + PrivKey: privKey, + PubKey: pubKey, + ConsKey: ed25519.GenPrivKeyFromSecret(privkeySeed), + AddressBech32: addr.String(), } i++ } diff --git a/types/simulation/config.go b/types/simulation/config.go index 07cd6d4c69d5..6f93684d0822 100644 --- a/types/simulation/config.go +++ b/types/simulation/config.go @@ -25,7 +25,8 @@ type Config struct { DBBackend string // custom db backend type BlockMaxGas int64 // custom max gas for block FuzzSeed []byte - T testing.TB + TB testing.TB + FauxMerkle bool } func (c Config) shallowCopy() Config { @@ -33,10 +34,10 @@ func (c Config) shallowCopy() Config { } // With sets the values of t, seed, and fuzzSeed in a copy of the Config and returns the copy. -func (c Config) With(t *testing.T, seed int64, fuzzSeed []byte) Config { - t.Helper() +func (c Config) With(tb testing.TB, seed int64, fuzzSeed []byte) Config { + tb.Helper() r := c.shallowCopy() - r.T = t + r.TB = tb r.Seed = seed r.FuzzSeed = fuzzSeed return r diff --git a/types/simulation/types.go b/types/simulation/types.go index c576b2885d06..cf0fbe1b1816 100644 --- a/types/simulation/types.go +++ b/types/simulation/types.go @@ -3,12 +3,9 @@ package simulation import ( "context" "encoding/json" - "fmt" "math/rand" "time" - "github.com/cosmos/gogoproto/proto" - "cosmossdk.io/core/address" sdk "github.com/cosmos/cosmos-sdk/types" @@ -20,6 +17,17 @@ type AppEntrypoint interface { SimDeliver(_txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) } +var _ AppEntrypoint = SimDeliverFn(nil) + +type ( + AppEntrypointFn = SimDeliverFn + SimDeliverFn func(_txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) +) + +func (m SimDeliverFn) SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { + return m(txEncoder, tx) +} + // Deprecated: Use WeightedProposalMsg instead. type WeightedProposalContent interface { AppParamsKey() string // key used to retrieve the value of the weight from the simulation application params @@ -28,7 +36,7 @@ type WeightedProposalContent interface { } // Deprecated: Use MsgSimulatorFn instead. -type ContentSimulatorFn func(r *rand.Rand, ctx sdk.Context, accs []Account) Content +type ContentSimulatorFn func(r *rand.Rand, ctx context.Context, accs []Account) Content // Deprecated: Use MsgSimulatorFn instead. type Content interface { @@ -85,17 +93,15 @@ type OperationMsg struct { Name string `json:"name" yaml:"name"` // operation name (msg Type or "no-operation") Comment string `json:"comment" yaml:"comment"` // additional comment OK bool `json:"ok" yaml:"ok"` // success - Msg []byte `json:"msg" yaml:"msg"` // protobuf encoded msg } // NewOperationMsgBasic creates a new operation message from raw input. -func NewOperationMsgBasic(moduleName, msgType, comment string, ok bool, msg []byte) OperationMsg { +func NewOperationMsgBasic(moduleName, msgType, comment string, ok bool) OperationMsg { return OperationMsg{ Route: moduleName, Name: msgType, Comment: comment, OK: ok, - Msg: msg, } } @@ -106,17 +112,12 @@ func NewOperationMsg(msg sdk.Msg, ok bool, comment string) OperationMsg { if moduleName == "" { moduleName = msgType } - protoBz, err := proto.Marshal(msg) - if err != nil { - panic(fmt.Errorf("failed to marshal proto message: %w", err)) - } - - return NewOperationMsgBasic(moduleName, msgType, comment, ok, protoBz) + return NewOperationMsgBasic(moduleName, msgType, comment, ok) } // NoOpMsg - create a no-operation message func NoOpMsg(moduleName, msgType, comment string) OperationMsg { - return NewOperationMsgBasic(moduleName, msgType, comment, false, nil) + return NewOperationMsgBasic(moduleName, msgType, comment, false) } // log entry text for this operation msg diff --git a/x/auth/module.go b/x/auth/module.go index 095304de78eb..b959fcde5408 100644 --- a/x/auth/module.go +++ b/x/auth/module.go @@ -15,6 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simsx" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" @@ -188,17 +189,12 @@ func (am AppModule) GenerateGenesisState(simState *module.SimulationState) { simulation.RandomizedGenState(simState, am.randGenAccountsFn) } -// ProposalMsgs returns msgs used for governance proposals for simulations. -func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { - return simulation.ProposalMsgs() +// ProposalMsgsX returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_update_params", 100), simulation.MsgUpdateParamsFactory()) } // RegisterStoreDecoder registers a decoder for auth module's types func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.accountKeeper.Schema) } - -// WeightedOperations doesn't return any auth module operation. -func (AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { - return nil -} diff --git a/x/auth/simulation/genesis.go b/x/auth/simulation/genesis.go index c6984cc355e8..7c18882fd325 100644 --- a/x/auth/simulation/genesis.go +++ b/x/auth/simulation/genesis.go @@ -1,8 +1,6 @@ package simulation import ( - "encoding/json" - "fmt" "math/rand" sdk "github.com/cosmos/cosmos-sdk/types" @@ -113,10 +111,5 @@ func RandomizedGenState(simState *module.SimulationState, randGenAccountsFn type authGenesis := types.NewGenesisState(params, genesisAccs) - bz, err := json.MarshalIndent(&authGenesis.Params, "", " ") - if err != nil { - panic(err) - } - fmt.Printf("Selected randomly generated auth parameters:\n%s\n", bz) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(authGenesis) } diff --git a/x/auth/simulation/msg_factory.go b/x/auth/simulation/msg_factory.go new file mode 100644 index 000000000000..83bcca3029d4 --- /dev/null +++ b/x/auth/simulation/msg_factory.go @@ -0,0 +1,25 @@ +package simulation + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/simsx" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +func MsgUpdateParamsFactory() simsx.SimMsgFactoryFn[*types.MsgUpdateParams] { + return func(_ context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgUpdateParams) { + r := testData.Rand() + params := types.DefaultParams() + params.MaxMemoCharacters = r.Uint64InRange(1, 1000) + params.TxSigLimit = r.Uint64InRange(1, 1000) + params.TxSizeCostPerByte = r.Uint64InRange(1, 1000) + params.SigVerifyCostED25519 = r.Uint64InRange(1, 1000) + params.SigVerifyCostSecp256k1 = r.Uint64InRange(1, 1000) + + return nil, &types.MsgUpdateParams{ + Authority: testData.ModuleAccountAddress(reporter, "gov"), + Params: params, + } + } +} diff --git a/x/auth/simulation/proposals.go b/x/auth/simulation/proposals.go deleted file mode 100644 index 930d9153fd88..000000000000 --- a/x/auth/simulation/proposals.go +++ /dev/null @@ -1,50 +0,0 @@ -package simulation - -import ( - "context" - "math/rand" - - coreaddress "cosmossdk.io/core/address" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// Simulation operation weights constants -const ( - DefaultWeightMsgUpdateParams int = 100 - - OpWeightMsgUpdateParams = "op_weight_msg_update_params" -) - -// ProposalMsgs defines the module weighted proposals' contents -func ProposalMsgs() []simtypes.WeightedProposalMsg { - return []simtypes.WeightedProposalMsg{ - simulation.NewWeightedProposalMsgX( - OpWeightMsgUpdateParams, - DefaultWeightMsgUpdateParams, - SimulateMsgUpdateParams, - ), - } -} - -// SimulateMsgUpdateParams returns a random MsgUpdateParams -func SimulateMsgUpdateParams(_ context.Context, r *rand.Rand, _ []simtypes.Account, _ coreaddress.Codec) (sdk.Msg, error) { - // use the default gov module account address as authority - var authority sdk.AccAddress = address.Module("gov") - - params := types.DefaultParams() - params.MaxMemoCharacters = uint64(simtypes.RandIntBetween(r, 1, 1000)) - params.TxSigLimit = uint64(simtypes.RandIntBetween(r, 1, 1000)) - params.TxSizeCostPerByte = uint64(simtypes.RandIntBetween(r, 1, 1000)) - params.SigVerifyCostED25519 = uint64(simtypes.RandIntBetween(r, 1, 1000)) - params.SigVerifyCostSecp256k1 = uint64(simtypes.RandIntBetween(r, 1, 1000)) - - return &types.MsgUpdateParams{ - Authority: authority.String(), - Params: params, - }, nil -} diff --git a/x/auth/simulation/proposals_test.go b/x/auth/simulation/proposals_test.go deleted file mode 100644 index f43e105d3056..000000000000 --- a/x/auth/simulation/proposals_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package simulation_test - -import ( - "math/rand" - "testing" - - "gotest.tools/v3/assert" - - codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/auth/simulation" - "github.com/cosmos/cosmos-sdk/x/auth/types" -) - -func TestProposalMsgs(t *testing.T) { - // initialize parameters - s := rand.NewSource(1) - r := rand.New(s) - - accounts := simtypes.RandomAccounts(r, 3) - - // execute ProposalMsgs function - weightedProposalMsgs := simulation.ProposalMsgs() - assert.Assert(t, len(weightedProposalMsgs) == 1) - - w0 := weightedProposalMsgs[0] - - // tests w0 interface: - assert.Equal(t, simulation.OpWeightMsgUpdateParams, w0.AppParamsKey()) - assert.Equal(t, simulation.DefaultWeightMsgUpdateParams, w0.DefaultWeight()) - - msg, err := w0.MsgSimulatorFn()(sdk.Context{}, r, accounts, codectestutil.CodecOptions{}.GetAddressCodec()) - assert.NilError(t, err) - msgUpdateParams, ok := msg.(*types.MsgUpdateParams) - assert.Assert(t, ok) - - assert.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgUpdateParams.Authority) - assert.Equal(t, uint64(999), msgUpdateParams.Params.MaxMemoCharacters) - assert.Equal(t, uint64(905), msgUpdateParams.Params.TxSigLimit) - assert.Equal(t, uint64(151), msgUpdateParams.Params.TxSizeCostPerByte) - assert.Equal(t, uint64(213), msgUpdateParams.Params.SigVerifyCostED25519) - assert.Equal(t, uint64(539), msgUpdateParams.Params.SigVerifyCostSecp256k1) -} diff --git a/x/authz/module/depinject.go b/x/authz/module/depinject.go index 849471f8fa27..911bca8c1daa 100644 --- a/x/authz/module/depinject.go +++ b/x/authz/module/depinject.go @@ -29,7 +29,6 @@ type ModuleInputs struct { Cdc codec.Codec AccountKeeper authz.AccountKeeper - BankKeeper authz.BankKeeper Registry cdctypes.InterfaceRegistry Environment appmodule.Environment } @@ -43,6 +42,6 @@ type ModuleOutputs struct { func ProvideModule(in ModuleInputs) ModuleOutputs { k := keeper.NewKeeper(in.Environment, in.Cdc, in.AccountKeeper) - m := NewAppModule(in.Cdc, k, in.AccountKeeper, in.BankKeeper, in.Registry) + m := NewAppModule(in.Cdc, k, in.Registry) return ModuleOutputs{AuthzKeeper: k, Module: m} } diff --git a/x/authz/module/module.go b/x/authz/module/module.go index 1acb5727b2e8..d25ab35eba88 100644 --- a/x/authz/module/module.go +++ b/x/authz/module/module.go @@ -20,6 +20,7 @@ import ( sdkclient "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/simsx" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) @@ -40,27 +41,21 @@ var ( // AppModule implements the sdk.AppModule interface type AppModule struct { - cdc codec.Codec - keeper keeper.Keeper - accountKeeper authz.AccountKeeper - bankKeeper authz.BankKeeper - registry cdctypes.InterfaceRegistry + cdc codec.Codec + keeper keeper.Keeper + registry cdctypes.InterfaceRegistry } // NewAppModule creates a new AppModule object func NewAppModule( cdc codec.Codec, keeper keeper.Keeper, - ak authz.AccountKeeper, - bk authz.BankKeeper, registry cdctypes.InterfaceRegistry, ) AppModule { return AppModule{ - cdc: cdc, - keeper: keeper, - accountKeeper: ak, - bankKeeper: bk, - registry: registry, + cdc: cdc, + keeper: keeper, + registry: registry, } } @@ -166,11 +161,8 @@ func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[keeper.StoreKey] = simulation.NewDecodeStore(am.cdc) } -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return simulation.WeightedOperations( - am.registry, - simState.AppParams, simState.Cdc, simState.TxConfig, - am.accountKeeper, am.bankKeeper, am.keeper, - ) +func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_grant", 100), simulation.MsgGrantFactory()) + reg.Add(weights.Get("msg_revoke", 90), simulation.MsgRevokeFactory(am.keeper)) + reg.Add(weights.Get("msg_exec", 90), simulation.MsgExecFactory(am.keeper)) } diff --git a/x/authz/simulation/genesis.go b/x/authz/simulation/genesis.go index 59b2d299a46c..de650064407b 100644 --- a/x/authz/simulation/genesis.go +++ b/x/authz/simulation/genesis.go @@ -5,7 +5,6 @@ import ( "time" v1 "cosmossdk.io/api/cosmos/gov/v1" - "cosmossdk.io/core/address" sdkmath "cosmossdk.io/math" "cosmossdk.io/x/authz" banktypes "cosmossdk.io/x/bank/types" @@ -16,23 +15,31 @@ import ( simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) +// RandomizedGenState generates a random GenesisState for authz. +func RandomizedGenState(simState *module.SimulationState) { + var grants []authz.GrantAuthorization + simState.AppParams.GetOrGenerate("authz", &grants, simState.Rand, func(r *rand.Rand) { + grants = genGrant(r, simState.Accounts, simState.GenTimestamp) + }) + + authzGrantsGenesis := authz.NewGenesisState(grants) + + simState.GenState[authz.ModuleName] = simState.Cdc.MustMarshalJSON(authzGrantsGenesis) +} + // genGrant returns a slice of authorization grants. -func genGrant(r *rand.Rand, accounts []simtypes.Account, genT time.Time, cdc address.Codec) []authz.GrantAuthorization { +func genGrant(r *rand.Rand, accounts []simtypes.Account, genT time.Time) []authz.GrantAuthorization { authorizations := make([]authz.GrantAuthorization, len(accounts)-1) for i := 0; i < len(accounts)-1; i++ { - granter := accounts[i] - grantee := accounts[i+1] var expiration *time.Time if i%3 != 0 { // generate some grants with no expire time e := genT.AddDate(1, 0, 0) expiration = &e } - granterAddr, _ := cdc.BytesToString(granter.Address) - granteeAddr, _ := cdc.BytesToString(grantee.Address) authorizations[i] = authz.GrantAuthorization{ - Granter: granterAddr, - Grantee: granteeAddr, - Authorization: generateRandomGrant(r, cdc), + Granter: accounts[i].AddressBech32, + Grantee: accounts[i+1].AddressBech32, + Authorization: generateRandomGrant(r), Expiration: expiration, } } @@ -40,32 +47,17 @@ func genGrant(r *rand.Rand, accounts []simtypes.Account, genT time.Time, cdc add return authorizations } -func generateRandomGrant(r *rand.Rand, addressCodec address.Codec) *codectypes.Any { - authorizations := make([]*codectypes.Any, 2) - sendAuthz := banktypes.NewSendAuthorization(sdk.NewCoins(sdk.NewCoin("stake", sdkmath.NewInt(1000))), nil, addressCodec) - authorizations[0] = newAnyAuthorization(sendAuthz) - authorizations[1] = newAnyAuthorization(authz.NewGenericAuthorization(sdk.MsgTypeURL(&v1.MsgSubmitProposal{}))) - - return authorizations[r.Intn(len(authorizations))] +func generateRandomGrant(r *rand.Rand) *codectypes.Any { + examples := []*codectypes.Any{ + must(codectypes.NewAnyWithValue(banktypes.NewSendAuthorization(sdk.NewCoins(sdk.NewCoin("stake", sdkmath.NewInt(1000))), nil, nil))), + must(codectypes.NewAnyWithValue(authz.NewGenericAuthorization(sdk.MsgTypeURL(&v1.MsgSubmitProposal{})))), + } + return examples[r.Intn(len(examples))] } -func newAnyAuthorization(a authz.Authorization) *codectypes.Any { - any, err := codectypes.NewAnyWithValue(a) +func must[T any](r T, err error) T { if err != nil { panic(err) } - - return any -} - -// RandomizedGenState generates a random GenesisState for authz. -func RandomizedGenState(simState *module.SimulationState) { - var grants []authz.GrantAuthorization - simState.AppParams.GetOrGenerate("authz", &grants, simState.Rand, func(r *rand.Rand) { - grants = genGrant(r, simState.Accounts, simState.GenTimestamp, simState.AddressCodec) - }) - - authzGrantsGenesis := authz.NewGenesisState(grants) - - simState.GenState[authz.ModuleName] = simState.Cdc.MustMarshalJSON(authzGrantsGenesis) + return r } diff --git a/x/authz/simulation/msg_factory.go b/x/authz/simulation/msg_factory.go new file mode 100644 index 000000000000..ab30a08ffee8 --- /dev/null +++ b/x/authz/simulation/msg_factory.go @@ -0,0 +1,103 @@ +package simulation + +import ( + "context" + "time" + + "cosmossdk.io/x/authz" + "cosmossdk.io/x/authz/keeper" + banktype "cosmossdk.io/x/bank/types" + + "github.com/cosmos/cosmos-sdk/simsx" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func MsgGrantFactory() simsx.SimMsgFactoryFn[*authz.MsgGrant] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *authz.MsgGrant) { + granter := testData.AnyAccount(reporter, simsx.WithSpendableBalance()) + grantee := testData.AnyAccount(reporter, simsx.ExcludeAccounts(granter)) + spendLimit := granter.LiquidBalance().RandSubsetCoins(reporter, simsx.WithSendEnabledCoins()) + + r := testData.Rand() + var expiration *time.Time + if t1 := r.Timestamp(); !t1.Before(simsx.BlockTime(ctx)) { + expiration = &t1 + } + // pick random authorization + authorizations := []authz.Authorization{ + banktype.NewSendAuthorization(spendLimit, nil, testData.AddressCodec()), + authz.NewGenericAuthorization(sdk.MsgTypeURL(&banktype.MsgSend{})), + } + randomAuthz := simsx.OneOf(r, authorizations) + + msg, err := authz.NewMsgGrant(granter.AddressBech32, grantee.AddressBech32, randomAuthz, expiration) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + return []simsx.SimAccount{granter}, msg + } +} + +func MsgExecFactory(k keeper.Keeper) simsx.SimMsgFactoryFn[*authz.MsgExec] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *authz.MsgExec) { + bankSendOnlyFilter := func(a authz.Authorization) bool { + _, ok := a.(*banktype.SendAuthorization) + return ok + } + granterAddr, granteeAddr, gAuthz := findGrant(ctx, k, reporter, bankSendOnlyFilter) + granter := testData.GetAccountbyAccAddr(reporter, granterAddr) + grantee := testData.GetAccountbyAccAddr(reporter, granteeAddr) + if reporter.IsSkipped() { + return nil, nil + } + amount := granter.LiquidBalance().RandSubsetCoins(reporter, simsx.WithSendEnabledCoins()) + amount = amount.Min(gAuthz.(*banktype.SendAuthorization).SpendLimit) + + payloadMsg := []sdk.Msg{banktype.NewMsgSend(granter.AddressBech32, grantee.AddressBech32, amount)} + msgExec := authz.NewMsgExec(grantee.AddressBech32, payloadMsg) + return []simsx.SimAccount{grantee}, &msgExec + } +} + +func MsgRevokeFactory(k keeper.Keeper) simsx.SimMsgFactoryFn[*authz.MsgRevoke] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *authz.MsgRevoke) { + granterAddr, granteeAddr, auth := findGrant(ctx, k, reporter) + granter := testData.GetAccountbyAccAddr(reporter, granterAddr) + grantee := testData.GetAccountbyAccAddr(reporter, granteeAddr) + if reporter.IsSkipped() { + return nil, nil + } + msgExec := authz.NewMsgRevoke(granter.AddressBech32, grantee.AddressBech32, auth.MsgTypeURL()) + return []simsx.SimAccount{granter}, &msgExec + } +} + +func findGrant( + ctx context.Context, + k keeper.Keeper, + reporter simsx.SimulationReporter, + acceptFilter ...func(a authz.Authorization) bool, +) (granterAddr, granteeAddr sdk.AccAddress, auth authz.Authorization) { + err := k.IterateGrants(ctx, func(granter, grantee sdk.AccAddress, grant authz.Grant) (bool, error) { + a, err2 := grant.GetAuthorization() + if err2 != nil { + return true, err2 + } + for _, filter := range acceptFilter { + if !filter(a) { + return false, nil + } + } + granterAddr, granteeAddr, auth = granter, grantee, a + return true, nil + }) + if err != nil { + reporter.Skip(err.Error()) + return + } + if auth == nil { + reporter.Skip("no grant found") + } + return +} diff --git a/x/authz/simulation/operations.go b/x/authz/simulation/operations.go deleted file mode 100644 index 4c7c085d3b70..000000000000 --- a/x/authz/simulation/operations.go +++ /dev/null @@ -1,394 +0,0 @@ -package simulation - -import ( - "context" - "math/rand" - "time" - - gogoprotoany "github.com/cosmos/gogoproto/types/any" - - "cosmossdk.io/core/address" - "cosmossdk.io/core/appmodule" - corecontext "cosmossdk.io/core/context" - coregas "cosmossdk.io/core/gas" - coreheader "cosmossdk.io/core/header" - "cosmossdk.io/x/authz" - "cosmossdk.io/x/authz/keeper" - banktype "cosmossdk.io/x/bank/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - cdctypes "github.com/cosmos/cosmos-sdk/codec/types" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// authz message types -var ( - TypeMsgGrant = sdk.MsgTypeURL(&authz.MsgGrant{}) - TypeMsgRevoke = sdk.MsgTypeURL(&authz.MsgRevoke{}) - TypeMsgExec = sdk.MsgTypeURL(&authz.MsgExec{}) -) - -// Simulation operation weights constants -const ( - OpWeightMsgGrant = "op_weight_msg_grant" - OpWeightRevoke = "op_weight_msg_revoke" - OpWeightExec = "op_weight_msg_execute" -) - -// authz operations weights -const ( - WeightGrant = 100 - WeightRevoke = 90 - WeightExec = 90 -) - -// WeightedOperations returns all the operations from the module with their respective weights -func WeightedOperations( - registry cdctypes.InterfaceRegistry, - appParams simtypes.AppParams, - cdc codec.JSONCodec, - txGen client.TxConfig, - ak authz.AccountKeeper, - bk authz.BankKeeper, - k keeper.Keeper, -) simulation.WeightedOperations { - var ( - weightMsgGrant int - weightExec int - weightRevoke int - ) - - appParams.GetOrGenerate(OpWeightMsgGrant, &weightMsgGrant, nil, func(_ *rand.Rand) { - weightMsgGrant = WeightGrant - }) - - appParams.GetOrGenerate(OpWeightExec, &weightExec, nil, func(_ *rand.Rand) { - weightExec = WeightExec - }) - - appParams.GetOrGenerate(OpWeightRevoke, &weightRevoke, nil, func(_ *rand.Rand) { - weightRevoke = WeightRevoke - }) - - pCdc := codec.NewProtoCodec(registry) - - return simulation.WeightedOperations{ - simulation.NewWeightedOperation( - weightMsgGrant, - SimulateMsgGrant(pCdc, txGen, ak, bk, k), - ), - simulation.NewWeightedOperation( - weightExec, - SimulateMsgExec(pCdc, txGen, ak, bk, k, registry), - ), - simulation.NewWeightedOperation( - weightRevoke, - SimulateMsgRevoke(pCdc, txGen, ak, bk, k), - ), - } -} - -// SimulateMsgGrant generates a MsgGrant with random values. -func SimulateMsgGrant( - cdc *codec.ProtoCodec, - txCfg client.TxConfig, - ak authz.AccountKeeper, - bk authz.BankKeeper, - _ keeper.Keeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - granter, _ := simtypes.RandomAcc(r, accs) - grantee, _ := simtypes.RandomAcc(r, accs) - - if granter.Address.Equals(grantee.Address) { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "granter and grantee are same"), nil, nil - } - - granterAcc := ak.GetAccount(ctx, granter.Address) - spendableCoins := bk.SpendableCoins(ctx, granter.Address) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, err.Error()), nil, err - } - - spendLimit := spendableCoins.Sub(fees...) - if len(spendLimit) == 0 { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "spend limit is nil"), nil, nil - } - - var expiration *time.Time - t1 := simtypes.RandTimestamp(r) - if !t1.Before(ctx.HeaderInfo().Time) { - expiration = &t1 - } - randomAuthz := generateRandomAuthorization(r, spendLimit, ak.AddressCodec()) - - granterAddr, err := ak.AddressCodec().BytesToString(granter.Address) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "could not get granter address"), nil, nil - } - granteeAddr, err := ak.AddressCodec().BytesToString(grantee.Address) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "could not get grantee address"), nil, nil - } - msg, err := authz.NewMsgGrant(granterAddr, granteeAddr, randomAuthz, expiration) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, err.Error()), nil, err - } - tx, err := simtestutil.GenSignedMockTx( - r, - txCfg, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{granterAcc.GetAccountNumber()}, - []uint64{granterAcc.GetSequence()}, - granter.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txCfg.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -func generateRandomAuthorization(r *rand.Rand, spendLimit sdk.Coins, addressCodec address.Codec) authz.Authorization { - authorizations := make([]authz.Authorization, 2) - sendAuthz := banktype.NewSendAuthorization(spendLimit, nil, addressCodec) - authorizations[0] = sendAuthz - authorizations[1] = authz.NewGenericAuthorization(sdk.MsgTypeURL(&banktype.MsgSend{})) - - return authorizations[r.Intn(len(authorizations))] -} - -// SimulateMsgRevoke generates a MsgRevoke with random values. -func SimulateMsgRevoke( - cdc *codec.ProtoCodec, - txCfg client.TxConfig, - ak authz.AccountKeeper, - bk authz.BankKeeper, - k keeper.Keeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - var granterAddr, granteeAddr sdk.AccAddress - var grant authz.Grant - hasGrant := false - - err := k.IterateGrants(ctx, func(granter, grantee sdk.AccAddress, g authz.Grant) (bool, error) { - grant = g - granterAddr = granter - granteeAddr = grantee - hasGrant = true - return true, nil - }) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, err.Error()), nil, err - } - - if !hasGrant { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "no grants"), nil, nil - } - - granterAcc, ok := simtypes.FindAccount(accs, granterAddr) - if !ok { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "account not found"), nil, sdkerrors.ErrNotFound.Wrapf("account not found") - } - - spendableCoins := bk.SpendableCoins(ctx, granterAddr) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "fee error"), nil, err - } - - a, err := grant.GetAuthorization() - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "authorization error"), nil, err - } - - granterStrAddr, err := ak.AddressCodec().BytesToString(granterAddr) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "could not get granter address"), nil, nil - } - granteeStrAddr, err := ak.AddressCodec().BytesToString(granteeAddr) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "could not get grantee address"), nil, nil - } - msg := authz.NewMsgRevoke(granterStrAddr, granteeStrAddr, a.MsgTypeURL()) - account := ak.GetAccount(ctx, granterAddr) - tx, err := simtestutil.GenSignedMockTx( - r, - txCfg, - []sdk.Msg{&msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - granterAcc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, err.Error()), nil, err - } - - _, _, err = app.SimDeliver(txCfg.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "unable to execute tx: "+err.Error()), nil, err - } - - return simtypes.NewOperationMsg(&msg, true, ""), nil, nil - } -} - -// SimulateMsgExec generates a MsgExec with random values. -func SimulateMsgExec( - cdc *codec.ProtoCodec, - txCfg client.TxConfig, - ak authz.AccountKeeper, - bk authz.BankKeeper, - k keeper.Keeper, - unpacker gogoprotoany.AnyUnpacker, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - var granterAddr sdk.AccAddress - var granteeAddr sdk.AccAddress - var sendAuth *banktype.SendAuthorization - var err error - err = k.IterateGrants(ctx, func(granter, grantee sdk.AccAddress, grant authz.Grant) (bool, error) { - granterAddr = granter - granteeAddr = grantee - var a authz.Authorization - a, err = grant.GetAuthorization() - if err != nil { - return true, err - } - var ok bool - sendAuth, ok = a.(*banktype.SendAuthorization) - return ok, nil - }) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err - } - if sendAuth == nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, "no grant found"), nil, nil - } - - grantee, ok := simtypes.FindAccount(accs, granteeAddr) - if !ok { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "Account not found"), nil, sdkerrors.ErrNotFound.Wrapf("grantee account not found") - } - - if _, ok := simtypes.FindAccount(accs, granterAddr); !ok { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "Account not found"), nil, sdkerrors.ErrNotFound.Wrapf("granter account not found") - } - - granterspendableCoins := bk.SpendableCoins(ctx, granterAddr) - coins := simtypes.RandSubsetCoins(r, granterspendableCoins) - // if coins slice is empty, we can not create valid banktype.MsgSend - if len(coins) == 0 { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, "empty coins slice"), nil, nil - } - - // Check send_enabled status of each sent coin denom - if err := bk.IsSendEnabledCoins(ctx, coins...); err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, nil - } - - graStr, err := ak.AddressCodec().BytesToString(granterAddr) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err - } - greStr, err := ak.AddressCodec().BytesToString(granteeAddr) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err - } - - msg := []sdk.Msg{banktype.NewMsgSend(graStr, greStr, coins)} - - goCtx := context.WithValue(ctx.Context(), corecontext.EnvironmentContextKey, appmodule.Environment{ - HeaderService: headerService{}, - GasService: mockGasService{}, - }) - - _, err = sendAuth.Accept(goCtx, msg[0]) - if err != nil { - if sdkerrors.ErrInsufficientFunds.Is(err) { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, nil - } - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err - - } - - msgExec := authz.NewMsgExec(greStr, msg) - granteeSpendableCoins := bk.SpendableCoins(ctx, granteeAddr) - fees, err := simtypes.RandomFees(r, granteeSpendableCoins) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, "fee error"), nil, err - } - - granteeAcc := ak.GetAccount(ctx, granteeAddr) - tx, err := simtestutil.GenSignedMockTx( - r, - txCfg, - []sdk.Msg{&msgExec}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{granteeAcc.GetAccountNumber()}, - []uint64{granteeAcc.GetSequence()}, - grantee.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err - } - - _, _, err = app.SimDeliver(txCfg.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err - } - - err = msgExec.UnpackInterfaces(unpacker) - if err != nil { - return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, "unmarshal error"), nil, err - } - return simtypes.NewOperationMsg(&msgExec, true, "success"), nil, nil - } -} - -type headerService struct{} - -func (h headerService) HeaderInfo(ctx context.Context) coreheader.Info { - return sdk.UnwrapSDKContext(ctx).HeaderInfo() -} - -type mockGasService struct { - coregas.Service -} - -func (m mockGasService) GasMeter(ctx context.Context) coregas.Meter { - return mockGasMeter{} -} - -type mockGasMeter struct { - coregas.Meter -} - -func (m mockGasMeter) Consume(amount coregas.Gas, descriptor string) error { - return nil -} diff --git a/x/bank/module.go b/x/bank/module.go index 721c3dc6a0cf..49aa2981b918 100644 --- a/x/bank/module.go +++ b/x/bank/module.go @@ -18,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simsx" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" @@ -160,19 +161,17 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { simulation.RandomizedGenState(simState) } -// ProposalMsgs returns msgs used for governance proposals for simulations. -func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { - return simulation.ProposalMsgs() -} - // RegisterStoreDecoder registers a decoder for supply module's types func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.keeper.(keeper.BaseKeeper).Schema) } -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return simulation.WeightedOperations( - simState.AppParams, simState.Cdc, simState.TxConfig, am.accountKeeper, am.keeper, - ) +// ProposalMsgsX returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_update_params", 100), simulation.MsgUpdateParamsFactory()) +} + +func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_send", 100), simulation.MsgSendFactory()) + reg.Add(weights.Get("msg_multisend", 10), simulation.MsgMultiSendFactory()) } diff --git a/x/bank/simulation/genesis.go b/x/bank/simulation/genesis.go index 9e8b8512b827..f2f9b2e2f907 100644 --- a/x/bank/simulation/genesis.go +++ b/x/bank/simulation/genesis.go @@ -1,8 +1,6 @@ package simulation import ( - "encoding/json" - "fmt" "math/rand" sdkmath "cosmossdk.io/math" @@ -99,10 +97,5 @@ func RandomizedGenState(simState *module.SimulationState) { SendEnabled: sendEnabled, } - paramsBytes, err := json.MarshalIndent(&bankGenesis.Params, "", " ") - if err != nil { - panic(err) - } - fmt.Printf("Selected randomly generated bank parameters:\n%s\n", paramsBytes) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&bankGenesis) } diff --git a/x/bank/simulation/msg_factory.go b/x/bank/simulation/msg_factory.go new file mode 100644 index 000000000000..ac9a9ff388a2 --- /dev/null +++ b/x/bank/simulation/msg_factory.go @@ -0,0 +1,87 @@ +package simulation + +import ( + "context" + "slices" + + "cosmossdk.io/x/bank/types" + + "github.com/cosmos/cosmos-sdk/simsx" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func MsgSendFactory() simsx.SimMsgFactoryFn[*types.MsgSend] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgSend) { + from := testData.AnyAccount(reporter, simsx.WithSpendableBalance()) + to := testData.AnyAccount(reporter, simsx.ExcludeAccounts(from)) + coins := from.LiquidBalance().RandSubsetCoins(reporter, simsx.WithSendEnabledCoins()) + return []simsx.SimAccount{from}, types.NewMsgSend(from.AddressBech32, to.AddressBech32, coins) + } +} + +func MsgMultiSendFactory() simsx.SimMsgFactoryFn[*types.MsgMultiSend] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgMultiSend) { + r := testData.Rand() + var ( + sending = make([]types.Input, 1) + receiving = make([]types.Output, r.Intn(3)+1) + senderAcc = make([]simsx.SimAccount, len(sending)) + totalSentCoins sdk.Coins + uniqueAccountsFilter = simsx.UniqueAccounts() + ) + for i := range sending { + // generate random input fields, ignore to address + from := testData.AnyAccount(reporter, simsx.WithSpendableBalance(), uniqueAccountsFilter) + if reporter.IsSkipped() { + return nil, nil + } + coins := from.LiquidBalance().RandSubsetCoins(reporter, simsx.WithSendEnabledCoins()) + + // set signer privkey + senderAcc[i] = from + + // set next input and accumulate total sent coins + sending[i] = types.NewInput(from.AddressBech32, coins) + totalSentCoins = totalSentCoins.Add(coins...) + } + + for i := range receiving { + receiver := testData.AnyAccount(reporter) + if reporter.IsSkipped() { + return nil, nil + } + + var outCoins sdk.Coins + // split total sent coins into random subsets for output + if i == len(receiving)-1 { + // last one receives remaining amount + outCoins = totalSentCoins + } else { + // take random subset of remaining coins for output + // and update remaining coins + outCoins = r.SubsetCoins(totalSentCoins) + totalSentCoins = totalSentCoins.Sub(outCoins...) + } + + receiving[i] = types.NewOutput(receiver.AddressBech32, outCoins) + } + + // remove any entries that have no coins + receiving = slices.DeleteFunc(receiving, func(o types.Output) bool { + return o.Address == "" || o.Coins.Empty() + }) + return senderAcc, &types.MsgMultiSend{Inputs: sending, Outputs: receiving} + } +} + +// MsgUpdateParamsFactory creates a gov proposal for param updates +func MsgUpdateParamsFactory() simsx.SimMsgFactoryFn[*types.MsgUpdateParams] { + return func(_ context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgUpdateParams) { + params := types.DefaultParams() + params.DefaultSendEnabled = testData.Rand().Intn(2) == 0 + return nil, &types.MsgUpdateParams{ + Authority: testData.ModuleAccountAddress(reporter, "gov"), + Params: params, + } + } +} diff --git a/x/bank/simulation/operations.go b/x/bank/simulation/operations.go deleted file mode 100644 index d662687cfe07..000000000000 --- a/x/bank/simulation/operations.go +++ /dev/null @@ -1,472 +0,0 @@ -package simulation - -import ( - "math/rand" - - "cosmossdk.io/x/bank/keeper" - "cosmossdk.io/x/bank/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// Simulation operation weights constants -const ( - OpWeightMsgSend = "op_weight_msg_send" - OpWeightMsgMultiSend = "op_weight_msg_multisend" - DefaultWeightMsgSend = 100 // from simappparams.DefaultWeightMsgSend - DefaultWeightMsgMultiSend = 10 // from simappparams.DefaultWeightMsgMultiSend - - distributionModuleName = "distribution" -) - -// WeightedOperations returns all the operations from the module with their respective weights -func WeightedOperations( - appParams simtypes.AppParams, - cdc codec.JSONCodec, - txGen client.TxConfig, - ak types.AccountKeeper, - bk keeper.Keeper, -) simulation.WeightedOperations { - var weightMsgSend, weightMsgMultiSend int - appParams.GetOrGenerate(OpWeightMsgSend, &weightMsgSend, nil, func(_ *rand.Rand) { - weightMsgSend = DefaultWeightMsgSend - }) - - appParams.GetOrGenerate(OpWeightMsgMultiSend, &weightMsgMultiSend, nil, func(_ *rand.Rand) { - weightMsgMultiSend = DefaultWeightMsgMultiSend - }) - - return simulation.WeightedOperations{ - simulation.NewWeightedOperation( - weightMsgSend, - SimulateMsgSend(txGen, ak, bk), - ), - simulation.NewWeightedOperation( - weightMsgMultiSend, - SimulateMsgMultiSend(txGen, ak, bk), - ), - } -} - -// SimulateMsgSend tests and runs a single msg send where both -// accounts already exist. -func SimulateMsgSend( - txGen client.TxConfig, - ak types.AccountKeeper, - bk keeper.Keeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, - accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgSend{}) - from, to, coins, skip := randomSendFields(r, ctx, accs, bk, ak) - - // if coins slice is empty, we can not create valid types.MsgSend - if len(coins) == 0 { - return simtypes.NoOpMsg(types.ModuleName, msgType, "empty coins slice"), nil, nil - } - - // Check send_enabled status of each coin denom - if err := bk.IsSendEnabledCoins(ctx, coins...); err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, err.Error()), nil, nil - } - if skip { - return simtypes.NoOpMsg(types.ModuleName, msgType, "skip all transfers"), nil, nil - } - - fromstr, err := ak.AddressCodec().BytesToString(from.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, err.Error()), nil, nil - } - tostr, err := ak.AddressCodec().BytesToString(to.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, err.Error()), nil, nil - } - - msg := types.NewMsgSend(fromstr, tostr, coins) - - if err := sendMsgSend(r, app, txGen, bk, ak, msg, ctx, chainID, []cryptotypes.PrivKey{from.PrivKey}); err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "invalid transfers"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, nil - } -} - -// SimulateMsgSendToModuleAccount tests and runs a single msg send where both -// accounts already exist. -func SimulateMsgSendToModuleAccount( - txGen client.TxConfig, - ak types.AccountKeeper, - bk keeper.Keeper, - moduleAccount int, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, - accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgSend{}) - from := accs[0] - to := getModuleAccounts(ak, ctx, moduleAccount)[0] - - spendable := bk.SpendableCoins(ctx, from.Address) - coins := simtypes.RandSubsetCoins(r, spendable) - // if coins slice is empty, we can not create valid types.MsgSend - if len(coins) == 0 { - return simtypes.NoOpMsg(types.ModuleName, msgType, "empty coins slice"), nil, nil - } - // Check send_enabled status of each coin denom - if err := bk.IsSendEnabledCoins(ctx, coins...); err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, err.Error()), nil, nil - } - fromstr, err := ak.AddressCodec().BytesToString(from.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, err.Error()), nil, nil - } - tostr, err := ak.AddressCodec().BytesToString(to.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, err.Error()), nil, nil - } - msg := types.NewMsgSend(fromstr, tostr, coins) - - if err := sendMsgSend(r, app, txGen, bk, ak, msg, ctx, chainID, []cryptotypes.PrivKey{from.PrivKey}); err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "invalid transfers"), nil, err - } - return simtypes.NewOperationMsg(msg, true, ""), nil, nil - } -} - -// sendMsgSend sends a transaction with a MsgSend from a provided random account. -func sendMsgSend( - r *rand.Rand, app simtypes.AppEntrypoint, - txGen client.TxConfig, - bk keeper.Keeper, ak types.AccountKeeper, - msg *types.MsgSend, ctx sdk.Context, chainID string, privkeys []cryptotypes.PrivKey, -) error { - var ( - fees sdk.Coins - err error - ) - - from, err := ak.AddressCodec().StringToBytes(msg.FromAddress) - if err != nil { - return err - } - - account := ak.GetAccount(ctx, from) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - coins, hasNeg := spendable.SafeSub(msg.Amount...) - if !hasNeg { - fees, err = simtypes.RandomFees(r, coins) - if err != nil { - return err - } - } - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - privkeys..., - ) - if err != nil { - return err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return err - } - - return nil -} - -// SimulateMsgMultiSend tests and runs a single msg multisend, with randomized, capped number of inputs/outputs. -// all accounts in msg fields exist in state -func SimulateMsgMultiSend(txGen client.TxConfig, ak types.AccountKeeper, bk keeper.Keeper) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, - accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgMultiSend{}) - - // random number of inputs/outputs between [1, 3] - inputs := make([]types.Input, r.Intn(1)+1) //nolint:staticcheck // SA4030: (*math/rand.Rand).Intn(n) generates a random value 0 <= x < n; that is, the generated values don't include n; r.Intn(1) therefore always returns 0 - outputs := make([]types.Output, r.Intn(3)+1) - - // collect signer privKeys - privs := make([]cryptotypes.PrivKey, len(inputs)) - - // use map to check if address already exists as input - usedAddrs := make(map[string]bool) - - var totalSentCoins sdk.Coins - for i := range inputs { - // generate random input fields, ignore to address - from, _, coins, skip := randomSendFields(r, ctx, accs, bk, ak) - - fromAddr, err := ak.AddressCodec().BytesToString(from.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "could not retrieve address"), nil, err - } - - // make sure account is fresh and not used in previous input - for usedAddrs[fromAddr] { - from, _, coins, skip = randomSendFields(r, ctx, accs, bk, ak) - } - - if skip { - return simtypes.NoOpMsg(types.ModuleName, msgType, "skip all transfers"), nil, nil - } - - // set input address in used address map - usedAddrs[fromAddr] = true - - // set signer privkey - privs[i] = from.PrivKey - - // set next input and accumulate total sent coins - inputs[i] = types.NewInput(fromAddr, coins) - totalSentCoins = totalSentCoins.Add(coins...) - } - - // Check send_enabled status of each sent coin denom - if err := bk.IsSendEnabledCoins(ctx, totalSentCoins...); err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, err.Error()), nil, nil - } - - for o := range outputs { - out, _ := simtypes.RandomAcc(r, accs) - outAddr, err := ak.AddressCodec().BytesToString(out.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "could not retrieve output address"), nil, err - } - - var outCoins sdk.Coins - // split total sent coins into random subsets for output - if o == len(outputs)-1 { - outCoins = totalSentCoins - } else { - // take random subset of remaining coins for output - // and update remaining coins - outCoins = simtypes.RandSubsetCoins(r, totalSentCoins) - totalSentCoins = totalSentCoins.Sub(outCoins...) - } - - outputs[o] = types.NewOutput(outAddr, outCoins) - } - - // remove any output that has no coins - - for i := 0; i < len(outputs); { - if outputs[i].Coins.Empty() { - outputs[i] = outputs[len(outputs)-1] - outputs = outputs[:len(outputs)-1] - } else { - // continue onto next coin - i++ - } - } - - msg := &types.MsgMultiSend{ - Inputs: inputs, - Outputs: outputs, - } - err := sendMsgMultiSend(r, app, txGen, bk, ak, msg, ctx, chainID, privs) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "invalid transfers"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, nil - } -} - -// SimulateMsgMultiSendToModuleAccount sends coins to Module Accounts -func SimulateMsgMultiSendToModuleAccount( - txGen client.TxConfig, - ak types.AccountKeeper, - bk keeper.Keeper, - moduleAccount int, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, - accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgMultiSend{}) - inputs := make([]types.Input, 2) - outputs := make([]types.Output, moduleAccount) - // collect signer privKeys - privs := make([]cryptotypes.PrivKey, len(inputs)) - var totalSentCoins sdk.Coins - for i := range inputs { - sender := accs[i] - privs[i] = sender.PrivKey - senderAddr, err := ak.AddressCodec().BytesToString(sender.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, err.Error()), nil, err - } - spendable := bk.SpendableCoins(ctx, sender.Address) - coins := simtypes.RandSubsetCoins(r, spendable) - inputs[i] = types.NewInput(senderAddr, coins) - totalSentCoins = totalSentCoins.Add(coins...) - } - if err := bk.IsSendEnabledCoins(ctx, totalSentCoins...); err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, err.Error()), nil, nil - } - moduleAccounts := getModuleAccounts(ak, ctx, moduleAccount) - for i := range outputs { - outAddr, err := ak.AddressCodec().BytesToString(moduleAccounts[i].Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "could not retrieve output address"), nil, err - } - - var outCoins sdk.Coins - // split total sent coins into random subsets for output - if i == len(outputs)-1 { - outCoins = totalSentCoins - } else { - // take random subset of remaining coins for output - // and update remaining coins - outCoins = simtypes.RandSubsetCoins(r, totalSentCoins) - totalSentCoins = totalSentCoins.Sub(outCoins...) - } - outputs[i] = types.NewOutput(outAddr, outCoins) - } - // remove any output that has no coins - for i := 0; i < len(outputs); { - if outputs[i].Coins.Empty() { - outputs[i] = outputs[len(outputs)-1] - outputs = outputs[:len(outputs)-1] - } else { - // continue onto next coin - i++ - } - } - msg := &types.MsgMultiSend{ - Inputs: inputs, - Outputs: outputs, - } - err := sendMsgMultiSend(r, app, txGen, bk, ak, msg, ctx, chainID, privs) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "invalid transfers"), nil, err - } - return simtypes.NewOperationMsg(msg, true, ""), nil, nil - } -} - -// sendMsgMultiSend sends a transaction with a MsgMultiSend from a provided random -// account. -func sendMsgMultiSend( - r *rand.Rand, app simtypes.AppEntrypoint, - txGen client.TxConfig, - bk keeper.Keeper, ak types.AccountKeeper, - msg *types.MsgMultiSend, ctx sdk.Context, chainID string, privkeys []cryptotypes.PrivKey, -) error { - accountNumbers := make([]uint64, len(msg.Inputs)) - sequenceNumbers := make([]uint64, len(msg.Inputs)) - for i := 0; i < len(msg.Inputs); i++ { - addr, err := ak.AddressCodec().StringToBytes(msg.Inputs[i].Address) - if err != nil { - panic(err) - } - - acc := ak.GetAccount(ctx, addr) - accountNumbers[i] = acc.GetAccountNumber() - sequenceNumbers[i] = acc.GetSequence() - } - var ( - fees sdk.Coins - err error - ) - - addr, err := ak.AddressCodec().StringToBytes(msg.Inputs[0].Address) - if err != nil { - panic(err) - } - // feePayer is the first signer, i.e. first input address - feePayer := ak.GetAccount(ctx, addr) - spendable := bk.SpendableCoins(ctx, feePayer.GetAddress()) - coins, hasNeg := spendable.SafeSub(msg.Inputs[0].Coins...) - if !hasNeg { - fees, err = simtypes.RandomFees(r, coins) - if err != nil { - return err - } - } - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - accountNumbers, - sequenceNumbers, - privkeys..., - ) - if err != nil { - return err - } - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return err - } - return nil -} - -// randomSendFields returns the sender and recipient simulation accounts as well -// as the transferred amount. -func randomSendFields( - r *rand.Rand, ctx sdk.Context, accs []simtypes.Account, bk keeper.Keeper, ak types.AccountKeeper, -) (simtypes.Account, simtypes.Account, sdk.Coins, bool) { - from, _ := simtypes.RandomAcc(r, accs) - to, _ := simtypes.RandomAcc(r, accs) - - // disallow sending money to yourself - for from.PubKey.Equals(to.PubKey) { - to, _ = simtypes.RandomAcc(r, accs) - } - - acc := ak.GetAccount(ctx, from.Address) - if acc == nil { - return from, to, nil, true - } - - spendable := bk.SpendableCoins(ctx, acc.GetAddress()) - - sendCoins := simtypes.RandSubsetCoins(r, spendable) - if sendCoins.Empty() { - return from, to, nil, true - } - - return from, to, sendCoins, false -} - -func getModuleAccounts(ak types.AccountKeeper, ctx sdk.Context, moduleAccount int) []simtypes.Account { - moduleAccounts := make([]simtypes.Account, moduleAccount) - - for i := 0; i < moduleAccount; i++ { - acc := ak.GetModuleAccount(ctx, distributionModuleName) - mAcc := simtypes.Account{ - Address: acc.GetAddress(), - PrivKey: nil, - ConsKey: nil, - PubKey: acc.GetPubKey(), - } - moduleAccounts[i] = mAcc - } - - return moduleAccounts -} diff --git a/x/distribution/depinject.go b/x/distribution/depinject.go index 1701e88b8bf2..4803b4d34c60 100644 --- a/x/distribution/depinject.go +++ b/x/distribution/depinject.go @@ -74,7 +74,7 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { authorityAddr, ) - m := NewAppModule(in.Cdc, k, in.AccountKeeper, in.BankKeeper, in.StakingKeeper) + m := NewAppModule(in.Cdc, k, in.StakingKeeper) return ModuleOutputs{ DistrKeeper: k, diff --git a/x/distribution/go.mod b/x/distribution/go.mod index 1d0ab2329dc9..4db3ed8813d0 100644 --- a/x/distribution/go.mod +++ b/x/distribution/go.mod @@ -24,7 +24,6 @@ require ( github.com/stretchr/testify v1.9.0 google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 google.golang.org/grpc v1.66.2 - gotest.tools/v3 v3.5.1 ) require ( @@ -163,6 +162,7 @@ require ( google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.1 // indirect pgregory.net/rapid v1.1.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/x/distribution/module.go b/x/distribution/module.go index db043b3bdabe..eb7de978c6d1 100644 --- a/x/distribution/module.go +++ b/x/distribution/module.go @@ -18,6 +18,7 @@ import ( sdkclient "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simsx" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" @@ -43,21 +44,14 @@ var ( type AppModule struct { cdc codec.Codec keeper keeper.Keeper - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper stakingKeeper types.StakingKeeper } // NewAppModule creates a new AppModule object -func NewAppModule( - cdc codec.Codec, keeper keeper.Keeper, accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, stakingKeeper types.StakingKeeper, -) AppModule { +func NewAppModule(cdc codec.Codec, keeper keeper.Keeper, stakingKeeper types.StakingKeeper) AppModule { return AppModule{ cdc: cdc, keeper: keeper, - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, stakingKeeper: stakingKeeper, } } @@ -173,20 +167,18 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { simulation.RandomizedGenState(simState) } -// ProposalMsgs returns msgs used for governance proposals for simulations. -func (AppModule) ProposalMsgs(_ module.SimulationState) []simtypes.WeightedProposalMsg { - return simulation.ProposalMsgs() -} - // RegisterStoreDecoder registers a decoder for distribution module's types func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) } -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return simulation.WeightedOperations( - simState.AppParams, simState.Cdc, simState.TxConfig, - am.accountKeeper, am.bankKeeper, am.keeper, am.stakingKeeper, - ) +// ProposalMsgsX returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_update_params", 100), simulation.MsgUpdateParamsFactory()) +} + +func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_set_withdraw_address", 50), simulation.MsgSetWithdrawAddressFactory(am.keeper)) + reg.Add(weights.Get("msg_withdraw_delegation_reward", 50), simulation.MsgWithdrawDelegatorRewardFactory(am.keeper, am.stakingKeeper)) + reg.Add(weights.Get("msg_withdraw_validator_commission", 50), simulation.MsgWithdrawValidatorCommissionFactory(am.keeper, am.stakingKeeper)) } diff --git a/x/distribution/simulation/genesis.go b/x/distribution/simulation/genesis.go index bccf952ad99d..4b0c42c9b467 100644 --- a/x/distribution/simulation/genesis.go +++ b/x/distribution/simulation/genesis.go @@ -1,8 +1,6 @@ package simulation import ( - "encoding/json" - "fmt" "math/rand" "cosmossdk.io/math" @@ -43,10 +41,5 @@ func RandomizedGenState(simState *module.SimulationState) { }, } - bz, err := json.MarshalIndent(&distrGenesis, "", " ") - if err != nil { - panic(err) - } - fmt.Printf("Selected randomly generated distribution parameters:\n%s\n", bz) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&distrGenesis) } diff --git a/x/distribution/simulation/msg_factory.go b/x/distribution/simulation/msg_factory.go new file mode 100644 index 000000000000..2a494dd90c31 --- /dev/null +++ b/x/distribution/simulation/msg_factory.go @@ -0,0 +1,125 @@ +package simulation + +import ( + "context" + "errors" + + "cosmossdk.io/collections" + sdkmath "cosmossdk.io/math" + "cosmossdk.io/x/distribution/keeper" + "cosmossdk.io/x/distribution/types" + + "github.com/cosmos/cosmos-sdk/simsx" +) + +func MsgSetWithdrawAddressFactory(k keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgSetWithdrawAddress] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgSetWithdrawAddress) { + switch enabled, err := k.GetWithdrawAddrEnabled(ctx); { + case err != nil: + reporter.Skip("error getting params") + return nil, nil + case !enabled: + reporter.Skip("withdrawal is not enabled") + return nil, nil + } + delegator := testData.AnyAccount(reporter) + withdrawer := testData.AnyAccount(reporter, simsx.ExcludeAccounts(delegator)) + msg := types.NewMsgSetWithdrawAddress(delegator.AddressBech32, withdrawer.AddressBech32) + return []simsx.SimAccount{delegator}, msg + } +} + +func MsgWithdrawDelegatorRewardFactory(k keeper.Keeper, sk types.StakingKeeper) simsx.SimMsgFactoryFn[*types.MsgWithdrawDelegatorReward] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgWithdrawDelegatorReward) { + delegator := testData.AnyAccount(reporter) + + delegations, err := sk.GetAllDelegatorDelegations(ctx, delegator.Address) + switch { + case err != nil: + reporter.Skipf("error getting delegations: %v", err) + return nil, nil + case len(delegations) == 0: + reporter.Skip("no delegations found") + return nil, nil + } + delegation := delegations[testData.Rand().Intn(len(delegations))] + + valAddr, err := sk.ValidatorAddressCodec().StringToBytes(delegation.GetValidatorAddr()) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + + var valOper string + switch validator, err := sk.Validator(ctx, valAddr); { + case err != nil: + reporter.Skip(err.Error()) + return nil, nil + case validator == nil: + reporter.Skipf("validator %s not found", delegation.GetValidatorAddr()) + return nil, nil + default: + valOper = validator.GetOperator() + } + // get outstanding rewards so we can first check if the withdrawable coins are sendable + outstanding, err := k.GetValidatorOutstandingRewardsCoins(ctx, valAddr) + if err != nil { + reporter.Skipf("get outstanding rewards: %v", err) + return nil, nil + } + + for _, v := range outstanding { + if !testData.IsSendEnabledDenom(v.Denom) { + reporter.Skipf("denom send not enabled: " + v.Denom) + return nil, nil + } + } + + msg := types.NewMsgWithdrawDelegatorReward(delegator.AddressBech32, valOper) + return []simsx.SimAccount{delegator}, msg + } +} + +func MsgWithdrawValidatorCommissionFactory(k keeper.Keeper, sk types.StakingKeeper) simsx.SimMsgFactoryFn[*types.MsgWithdrawValidatorCommission] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgWithdrawValidatorCommission) { + allVals, err := sk.GetAllValidators(ctx) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + val := simsx.OneOf(testData.Rand(), allVals) + valAddrBz, err := sk.ValidatorAddressCodec().StringToBytes(val.GetOperator()) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + + commission, err := k.ValidatorsAccumulatedCommission.Get(ctx, valAddrBz) + if err != nil && !errors.Is(err, collections.ErrNotFound) { + reporter.Skip(err.Error()) + return nil, nil + } + + if commission.Commission.IsZero() { + reporter.Skip("validator commission is zero") + return nil, nil + } + msg := types.NewMsgWithdrawValidatorCommission(val.GetOperator()) + valAccount := testData.GetAccountbyAccAddr(reporter, valAddrBz) + return []simsx.SimAccount{valAccount}, msg + } +} + +func MsgUpdateParamsFactory() simsx.SimMsgFactoryFn[*types.MsgUpdateParams] { + return func(_ context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgUpdateParams) { + r := testData.Rand() + params := types.DefaultParams() + params.CommunityTax = r.DecN(sdkmath.LegacyNewDec(1)) + params.WithdrawAddrEnabled = r.Intn(2) == 0 + + return nil, &types.MsgUpdateParams{ + Authority: testData.ModuleAccountAddress(reporter, "gov"), + Params: params, + } + } +} diff --git a/x/distribution/simulation/operations.go b/x/distribution/simulation/operations.go deleted file mode 100644 index 4b067cbe7174..000000000000 --- a/x/distribution/simulation/operations.go +++ /dev/null @@ -1,247 +0,0 @@ -package simulation - -import ( - "fmt" - "math/rand" - - "github.com/pkg/errors" - - "cosmossdk.io/collections" - "cosmossdk.io/x/distribution/keeper" - "cosmossdk.io/x/distribution/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/testutil" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// Simulation operation weights constants -const ( - OpWeightMsgSetWithdrawAddress = "op_weight_msg_set_withdraw_address" - OpWeightMsgWithdrawDelegationReward = "op_weight_msg_withdraw_delegation_reward" - OpWeightMsgWithdrawValidatorCommission = "op_weight_msg_withdraw_validator_commission" - - DefaultWeightMsgSetWithdrawAddress int = 50 - DefaultWeightMsgWithdrawDelegationReward int = 50 - DefaultWeightMsgWithdrawValidatorCommission int = 50 -) - -// WeightedOperations returns all the operations from the module with their respective weights -func WeightedOperations( - appParams simtypes.AppParams, - cdc codec.JSONCodec, - txConfig client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k keeper.Keeper, - sk types.StakingKeeper, -) simulation.WeightedOperations { - var weightMsgSetWithdrawAddress int - appParams.GetOrGenerate(OpWeightMsgSetWithdrawAddress, &weightMsgSetWithdrawAddress, nil, func(_ *rand.Rand) { - weightMsgSetWithdrawAddress = DefaultWeightMsgSetWithdrawAddress - }) - - var weightMsgWithdrawDelegationReward int - appParams.GetOrGenerate(OpWeightMsgWithdrawDelegationReward, &weightMsgWithdrawDelegationReward, nil, func(_ *rand.Rand) { - weightMsgWithdrawDelegationReward = DefaultWeightMsgWithdrawDelegationReward - }) - - var weightMsgWithdrawValidatorCommission int - appParams.GetOrGenerate(OpWeightMsgWithdrawValidatorCommission, &weightMsgWithdrawValidatorCommission, nil, func(_ *rand.Rand) { - weightMsgWithdrawValidatorCommission = DefaultWeightMsgWithdrawValidatorCommission - }) - - return simulation.WeightedOperations{ - simulation.NewWeightedOperation( - weightMsgSetWithdrawAddress, - SimulateMsgSetWithdrawAddress(txConfig, ak, bk, k), - ), - simulation.NewWeightedOperation( - weightMsgWithdrawDelegationReward, - SimulateMsgWithdrawDelegatorReward(txConfig, ak, bk, k, sk), - ), - simulation.NewWeightedOperation( - weightMsgWithdrawValidatorCommission, - SimulateMsgWithdrawValidatorCommission(txConfig, ak, bk, k, sk), - ), - } -} - -// SimulateMsgSetWithdrawAddress generates a MsgSetWithdrawAddress with random values. -func SimulateMsgSetWithdrawAddress(txConfig client.TxConfig, ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - isWithdrawAddrEnabled, err := k.GetWithdrawAddrEnabled(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgSetWithdrawAddress{}), "error getting params"), nil, err - } - - if !isWithdrawAddrEnabled { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgSetWithdrawAddress{}), "withdrawal is not enabled"), nil, nil - } - - simAccount, _ := simtypes.RandomAcc(r, accs) - simToAccount, _ := simtypes.RandomAcc(r, accs) - - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - addr, err := ak.AddressCodec().BytesToString(simAccount.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgSetWithdrawAddress{}), "error converting delegator address"), nil, err - } - toAddr, err := ak.AddressCodec().BytesToString(simToAccount.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgSetWithdrawAddress{}), "error converting withdraw address"), nil, err - } - - msg := types.NewMsgSetWithdrawAddress(addr, toAddr) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txConfig, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// SimulateMsgWithdrawDelegatorReward generates a MsgWithdrawDelegatorReward with random values. -func SimulateMsgWithdrawDelegatorReward(txConfig client.TxConfig, ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper, sk types.StakingKeeper) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - simAccount, _ := simtypes.RandomAcc(r, accs) - delegations, err := sk.GetAllDelegatorDelegations(ctx, simAccount.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgWithdrawDelegatorReward{}), "error getting delegations"), nil, err - } - if len(delegations) == 0 { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgWithdrawDelegatorReward{}), "number of delegators equal 0"), nil, nil - } - - delegation := delegations[r.Intn(len(delegations))] - - delAddr, err := sk.ValidatorAddressCodec().StringToBytes(delegation.GetValidatorAddr()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgWithdrawDelegatorReward{}), "error converting validator address"), nil, err - } - validator, err := sk.Validator(ctx, delAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgWithdrawDelegatorReward{}), "error getting validator"), nil, err - } - if validator == nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgWithdrawDelegatorReward{}), "validator is nil"), nil, fmt.Errorf("validator %s not found", delegation.GetValidatorAddr()) - } - - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - addr, err := ak.AddressCodec().BytesToString(simAccount.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgWithdrawDelegatorReward{}), "error converting delegator address"), nil, err - } - - msg := types.NewMsgWithdrawDelegatorReward(addr, validator.GetOperator()) - - // get outstanding rewards so we can first check if the withdrawable coins are sendable - outstanding, err := k.GetValidatorOutstandingRewardsCoins(ctx, sdk.ValAddress(delAddr)) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgWithdrawDelegatorReward{}), "error getting outstanding rewards"), nil, err - } - - for _, v := range outstanding { - if !bk.IsSendEnabledDenom(ctx, v.Denom) { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "denom send not enabled: "+v.Denom), nil, nil - } - } - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txConfig, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// SimulateMsgWithdrawValidatorCommission generates a MsgWithdrawValidatorCommission with random values. -func SimulateMsgWithdrawValidatorCommission(txConfig client.TxConfig, ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper, sk types.StakingKeeper) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgWithdrawValidatorCommission{}) - - allVals, err := sk.GetAllValidators(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting all validators"), nil, err - } - - validator, ok := testutil.RandSliceElem(r, allVals) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, msgType, "random validator is not ok"), nil, nil - } - - valBz, err := sk.ValidatorAddressCodec().StringToBytes(validator.GetOperator()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error converting validator address"), nil, err - } - - commission, err := k.ValidatorsAccumulatedCommission.Get(ctx, valBz) - if err != nil && !errors.Is(err, collections.ErrNotFound) { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator commission"), nil, err - } - - if commission.Commission.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, msgType, "validator commission is zero"), nil, nil - } - - simAccount, found := simtypes.FindAccount(accs, sdk.AccAddress(valBz)) - if !found { - return simtypes.NoOpMsg(types.ModuleName, msgType, "could not find account"), nil, fmt.Errorf("validator %s not found", validator.GetOperator()) - } - - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - msg := types.NewMsgWithdrawValidatorCommission(validator.GetOperator()) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txConfig, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} diff --git a/x/distribution/simulation/proposals.go b/x/distribution/simulation/proposals.go deleted file mode 100644 index 3d9dfe6bd900..000000000000 --- a/x/distribution/simulation/proposals.go +++ /dev/null @@ -1,53 +0,0 @@ -package simulation - -import ( - "context" - "math/rand" - - coreaddress "cosmossdk.io/core/address" - sdkmath "cosmossdk.io/math" - "cosmossdk.io/x/distribution/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// Simulation operation weights constants -const ( - DefaultWeightMsgUpdateParams int = 50 - - OpWeightMsgUpdateParams = "op_weight_msg_update_params" -) - -// ProposalMsgs defines the module weighted proposals' contents -func ProposalMsgs() []simtypes.WeightedProposalMsg { - return []simtypes.WeightedProposalMsg{ - simulation.NewWeightedProposalMsgX( - OpWeightMsgUpdateParams, - DefaultWeightMsgUpdateParams, - SimulateMsgUpdateParams, - ), - } -} - -// SimulateMsgUpdateParams returns a random MsgUpdateParams -func SimulateMsgUpdateParams(_ context.Context, r *rand.Rand, _ []simtypes.Account, cdc coreaddress.Codec) (sdk.Msg, error) { - // use the default gov module account address as authority - var authority sdk.AccAddress = address.Module("gov") - - params := types.DefaultParams() - params.CommunityTax = simtypes.RandomDecAmount(r, sdkmath.LegacyNewDec(1)) - params.WithdrawAddrEnabled = r.Intn(2) == 0 - - authorityAddr, err := cdc.BytesToString(authority) - if err != nil { - return nil, err - } - - return &types.MsgUpdateParams{ - Authority: authorityAddr, - Params: params, - }, nil -} diff --git a/x/distribution/simulation/proposals_test.go b/x/distribution/simulation/proposals_test.go deleted file mode 100644 index ce792ea856e4..000000000000 --- a/x/distribution/simulation/proposals_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package simulation_test - -import ( - "context" - "math/rand" - "testing" - - "gotest.tools/v3/assert" - - sdkmath "cosmossdk.io/math" - "cosmossdk.io/x/distribution/simulation" - "cosmossdk.io/x/distribution/types" - - codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -func TestProposalMsgs(t *testing.T) { - // initialize parameters - s := rand.NewSource(1) - r := rand.New(s) - addressCodec := codectestutil.CodecOptions{}.GetAddressCodec() - accounts := simtypes.RandomAccounts(r, 3) - - // execute ProposalMsgs function - weightedProposalMsgs := simulation.ProposalMsgs() - assert.Assert(t, len(weightedProposalMsgs) == 1) - - w0 := weightedProposalMsgs[0] - - // tests w0 interface: - assert.Equal(t, simulation.OpWeightMsgUpdateParams, w0.AppParamsKey()) - assert.Equal(t, simulation.DefaultWeightMsgUpdateParams, w0.DefaultWeight()) - - msg, err := w0.MsgSimulatorFn()(context.Background(), r, accounts, addressCodec) - assert.NilError(t, err) - msgUpdateParams, ok := msg.(*types.MsgUpdateParams) - assert.Assert(t, ok) - - addr, err := addressCodec.BytesToString(sdk.AccAddress(address.Module("gov"))) - assert.NilError(t, err) - assert.Equal(t, addr, msgUpdateParams.Authority) - assert.DeepEqual(t, sdkmath.LegacyNewDec(0), msgUpdateParams.Params.CommunityTax) - assert.Equal(t, true, msgUpdateParams.Params.WithdrawAddrEnabled) -} diff --git a/x/epochs/module.go b/x/epochs/module.go index 6930d82174a2..2fba7c0de141 100644 --- a/x/epochs/module.go +++ b/x/epochs/module.go @@ -125,8 +125,3 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.keeper.Schema) } - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return nil -} diff --git a/x/epochs/simulation/genesis.go b/x/epochs/simulation/genesis.go index c236faacbb8a..8aa17add5bbc 100644 --- a/x/epochs/simulation/genesis.go +++ b/x/epochs/simulation/genesis.go @@ -1,8 +1,6 @@ package simulation import ( - "encoding/json" - "fmt" "math/rand" "strconv" "time" @@ -37,10 +35,5 @@ func RandomizedGenState(simState *module.SimulationState) { Epochs: epochs, } - bz, err := json.MarshalIndent(&epochsGenesis, "", " ") - if err != nil { - panic(err) - } - fmt.Printf("Selected randomly generated epochs parameters:\n%s\n", bz) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&epochsGenesis) } diff --git a/x/evidence/module.go b/x/evidence/module.go index 513563aec10d..0327a83915ef 100644 --- a/x/evidence/module.go +++ b/x/evidence/module.go @@ -152,8 +152,3 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.keeper.Schema) } - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return nil -} diff --git a/x/evidence/simulation/genesis.go b/x/evidence/simulation/genesis.go index 24b956274695..d4a329d203bb 100644 --- a/x/evidence/simulation/genesis.go +++ b/x/evidence/simulation/genesis.go @@ -1,8 +1,6 @@ package simulation import ( - "encoding/json" - "fmt" "math/rand" "cosmossdk.io/x/evidence/exported" @@ -28,10 +26,5 @@ func RandomizedGenState(simState *module.SimulationState) { evidenceGenesis := types.NewGenesisState(ev) - bz, err := json.MarshalIndent(&evidenceGenesis, "", " ") - if err != nil { - panic(err) - } - fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, bz) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(evidenceGenesis) } diff --git a/x/feegrant/module/depinject.go b/x/feegrant/module/depinject.go index 018b09d3d593..613d51a14743 100644 --- a/x/feegrant/module/depinject.go +++ b/x/feegrant/module/depinject.go @@ -7,9 +7,13 @@ import ( "cosmossdk.io/depinject/appconfig" "cosmossdk.io/x/feegrant" "cosmossdk.io/x/feegrant/keeper" + "cosmossdk.io/x/feegrant/simulation" "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/simsx" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) var _ depinject.OnePerModuleType = AppModule{} @@ -35,6 +39,23 @@ type FeegrantInputs struct { func ProvideModule(in FeegrantInputs) (keeper.Keeper, appmodule.AppModule) { k := keeper.NewKeeper(in.Environment, in.Cdc, in.AccountKeeper) - m := NewAppModule(in.Cdc, in.AccountKeeper, in.BankKeeper, k, in.Registry) + m := NewAppModule(in.Cdc, k, in.Registry) return k, m } + +// AppModuleSimulation functions + +// GenerateGenesisState creates a randomized GenState of the feegrant module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// RegisterStoreDecoder registers a decoder for feegrant module's types +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { + sdr[feegrant.StoreKey] = simulation.NewDecodeStore(am.cdc) +} + +func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_grant_fee_allowance", 100), simulation.MsgGrantAllowanceFactory(am.keeper)) + reg.Add(weights.Get("msg_grant_revoke_allowance", 100), simulation.MsgRevokeAllowanceFactory(am.keeper)) +} diff --git a/x/feegrant/module/module.go b/x/feegrant/module/module.go index c6c79fa396e3..2e22fef937bc 100644 --- a/x/feegrant/module/module.go +++ b/x/feegrant/module/module.go @@ -15,13 +15,11 @@ import ( "cosmossdk.io/x/feegrant" "cosmossdk.io/x/feegrant/client/cli" "cosmossdk.io/x/feegrant/keeper" - "cosmossdk.io/x/feegrant/simulation" sdkclient "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) var ( @@ -41,19 +39,15 @@ type AppModule struct { cdc codec.Codec registry cdctypes.InterfaceRegistry - keeper keeper.Keeper - accountKeeper feegrant.AccountKeeper - bankKeeper feegrant.BankKeeper + keeper keeper.Keeper } // NewAppModule creates a new AppModule object -func NewAppModule(cdc codec.Codec, ak feegrant.AccountKeeper, bk feegrant.BankKeeper, keeper keeper.Keeper, registry cdctypes.InterfaceRegistry) AppModule { +func NewAppModule(cdc codec.Codec, keeper keeper.Keeper, registry cdctypes.InterfaceRegistry) AppModule { return AppModule{ - cdc: cdc, - keeper: keeper, - accountKeeper: ak, - bankKeeper: bk, - registry: registry, + cdc: cdc, + keeper: keeper, + registry: registry, } } @@ -153,23 +147,3 @@ func (AppModule) ConsensusVersion() uint64 { return 2 } func (am AppModule) EndBlock(ctx context.Context) error { return EndBlocker(ctx, am.keeper) } - -// AppModuleSimulation functions - -// GenerateGenesisState creates a randomized GenState of the feegrant module. -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - simulation.RandomizedGenState(simState) -} - -// RegisterStoreDecoder registers a decoder for feegrant module's types -func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { - sdr[feegrant.StoreKey] = simulation.NewDecodeStore(am.cdc) -} - -// WeightedOperations returns all the feegrant module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return simulation.WeightedOperations( - simState.AppParams, simState.TxConfig, - am.accountKeeper, am.bankKeeper, am.keeper, - ) -} diff --git a/x/feegrant/simulation/msg_factory.go b/x/feegrant/simulation/msg_factory.go new file mode 100644 index 000000000000..fd8bacb88745 --- /dev/null +++ b/x/feegrant/simulation/msg_factory.go @@ -0,0 +1,58 @@ +package simulation + +import ( + "context" + + "cosmossdk.io/x/feegrant" + "cosmossdk.io/x/feegrant/keeper" + + "github.com/cosmos/cosmos-sdk/simsx" +) + +func MsgGrantAllowanceFactory(k keeper.Keeper) simsx.SimMsgFactoryFn[*feegrant.MsgGrantAllowance] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *feegrant.MsgGrantAllowance) { + granter := testData.AnyAccount(reporter, simsx.WithSpendableBalance()) + grantee := testData.AnyAccount(reporter, simsx.ExcludeAccounts(granter)) + if reporter.IsSkipped() { + return nil, nil + } + if f, _ := k.GetAllowance(ctx, granter.Address, grantee.Address); f != nil { + reporter.Skip("fee allowance exists") + return nil, nil + } + + coins := granter.LiquidBalance().RandSubsetCoins(reporter, simsx.WithSendEnabledCoins()) + oneYear := simsx.BlockTime(ctx).AddDate(1, 0, 0) + msg, err := feegrant.NewMsgGrantAllowance( + &feegrant.BasicAllowance{SpendLimit: coins, Expiration: &oneYear}, + granter.AddressBech32, + grantee.AddressBech32, + ) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + return []simsx.SimAccount{granter}, msg + } +} + +func MsgRevokeAllowanceFactory(k keeper.Keeper) simsx.SimMsgFactoryFn[*feegrant.MsgRevokeAllowance] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *feegrant.MsgRevokeAllowance) { + var gotGrant *feegrant.Grant + if err := k.IterateAllFeeAllowances(ctx, func(grant feegrant.Grant) bool { + gotGrant = &grant + return true + }); err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + if gotGrant == nil { + reporter.Skip("no grant found") + return nil, nil + } + granter := testData.GetAccount(reporter, gotGrant.Granter) + grantee := testData.GetAccount(reporter, gotGrant.Grantee) + msg := feegrant.NewMsgRevokeAllowance(granter.AddressBech32, grantee.AddressBech32) + return []simsx.SimAccount{granter}, &msg + } +} diff --git a/x/feegrant/simulation/operations.go b/x/feegrant/simulation/operations.go deleted file mode 100644 index 65bd53d528e9..000000000000 --- a/x/feegrant/simulation/operations.go +++ /dev/null @@ -1,197 +0,0 @@ -package simulation - -import ( - "math/rand" - - "cosmossdk.io/x/feegrant" - "cosmossdk.io/x/feegrant/keeper" - - "github.com/cosmos/cosmos-sdk/client" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// Simulation operation weights constants -const ( - OpWeightMsgGrantAllowance = "op_weight_msg_grant_fee_allowance" - OpWeightMsgRevokeAllowance = "op_weight_msg_grant_revoke_allowance" - DefaultWeightGrantAllowance int = 100 - DefaultWeightRevokeAllowance int = 100 -) - -var ( - TypeMsgGrantAllowance = sdk.MsgTypeURL(&feegrant.MsgGrantAllowance{}) - TypeMsgRevokeAllowance = sdk.MsgTypeURL(&feegrant.MsgRevokeAllowance{}) -) - -func WeightedOperations( - appParams simtypes.AppParams, - txConfig client.TxConfig, - ak feegrant.AccountKeeper, - bk feegrant.BankKeeper, - k keeper.Keeper, -) simulation.WeightedOperations { - var ( - weightMsgGrantAllowance int - weightMsgRevokeAllowance int - ) - - appParams.GetOrGenerate(OpWeightMsgGrantAllowance, &weightMsgGrantAllowance, nil, - func(_ *rand.Rand) { - weightMsgGrantAllowance = DefaultWeightGrantAllowance - }, - ) - - appParams.GetOrGenerate(OpWeightMsgRevokeAllowance, &weightMsgRevokeAllowance, nil, - func(_ *rand.Rand) { - weightMsgRevokeAllowance = DefaultWeightRevokeAllowance - }, - ) - - return simulation.WeightedOperations{ - simulation.NewWeightedOperation( - weightMsgGrantAllowance, - SimulateMsgGrantAllowance(txConfig, ak, bk, k), - ), - simulation.NewWeightedOperation( - weightMsgRevokeAllowance, - SimulateMsgRevokeAllowance(txConfig, ak, bk, k), - ), - } -} - -// SimulateMsgGrantAllowance generates MsgGrantAllowance with random values. -func SimulateMsgGrantAllowance( - txConfig client.TxConfig, - ak feegrant.AccountKeeper, - bk feegrant.BankKeeper, - k keeper.Keeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - granter, _ := simtypes.RandomAcc(r, accs) - grantee, _ := simtypes.RandomAcc(r, accs) - granterStr, err := ak.AddressCodec().BytesToString(granter.Address) - if err != nil { - return simtypes.OperationMsg{}, nil, err - } - granteeStr, err := ak.AddressCodec().BytesToString(grantee.Address) - if err != nil { - return simtypes.OperationMsg{}, nil, err - } - - if granteeStr == granterStr { - return simtypes.NoOpMsg(feegrant.ModuleName, TypeMsgGrantAllowance, "grantee and granter cannot be same"), nil, nil - } - - if f, _ := k.GetAllowance(ctx, granter.Address, grantee.Address); f != nil { - return simtypes.NoOpMsg(feegrant.ModuleName, TypeMsgGrantAllowance, "fee allowance exists"), nil, nil - } - - account := ak.GetAccount(ctx, granter.Address) - - spendableCoins := bk.SpendableCoins(ctx, account.GetAddress()) - if spendableCoins.Empty() { - return simtypes.NoOpMsg(feegrant.ModuleName, TypeMsgGrantAllowance, "unable to grant empty coins as SpendLimit"), nil, nil - } - - oneYear := ctx.HeaderInfo().Time.AddDate(1, 0, 0) - msg, err := feegrant.NewMsgGrantAllowance(&feegrant.BasicAllowance{ - SpendLimit: spendableCoins, - Expiration: &oneYear, - }, granterStr, granteeStr) - if err != nil { - return simtypes.NoOpMsg(feegrant.ModuleName, TypeMsgGrantAllowance, err.Error()), nil, err - } - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txConfig, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: granter, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: feegrant.ModuleName, - CoinsSpentInMsg: spendableCoins, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// SimulateMsgRevokeAllowance generates a MsgRevokeAllowance with random values. -func SimulateMsgRevokeAllowance( - txConfig client.TxConfig, - ak feegrant.AccountKeeper, - bk feegrant.BankKeeper, - k keeper.Keeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - hasGrant := false - - var granterAddr sdk.AccAddress - var granteeAddr sdk.AccAddress - err := k.IterateAllFeeAllowances(ctx, func(grant feegrant.Grant) bool { - granter, err := ak.AddressCodec().StringToBytes(grant.Granter) - if err != nil { - panic(err) - } - grantee, err := ak.AddressCodec().StringToBytes(grant.Grantee) - if err != nil { - panic(err) - } - granterAddr = granter - granteeAddr = grantee - hasGrant = true - return true - }) - if err != nil { - return simtypes.OperationMsg{}, nil, err - } - - if !hasGrant { - return simtypes.NoOpMsg(feegrant.ModuleName, TypeMsgRevokeAllowance, "no grants"), nil, nil - } - granter, ok := simtypes.FindAccount(accs, granterAddr) - - if !ok { - return simtypes.NoOpMsg(feegrant.ModuleName, TypeMsgRevokeAllowance, "Account not found"), nil, nil - } - - account := ak.GetAccount(ctx, granter.Address) - spendableCoins := bk.SpendableCoins(ctx, account.GetAddress()) - - granterStr, err := ak.AddressCodec().BytesToString(granterAddr) - if err != nil { - return simtypes.NoOpMsg(feegrant.ModuleName, TypeMsgRevokeAllowance, err.Error()), nil, err - } - granteeStr, err := ak.AddressCodec().BytesToString(granteeAddr) - if err != nil { - return simtypes.NoOpMsg(feegrant.ModuleName, TypeMsgRevokeAllowance, err.Error()), nil, err - } - msg := feegrant.NewMsgRevokeAllowance(granterStr, granteeStr) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txConfig, - Cdc: nil, - Msg: &msg, - Context: ctx, - SimAccount: granter, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: feegrant.ModuleName, - CoinsSpentInMsg: spendableCoins, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} diff --git a/x/genutil/client/cli/validate_genesis_test.go b/x/genutil/client/cli/validate_genesis_test.go index 2608f448d54b..0e4549099520 100644 --- a/x/genutil/client/cli/validate_genesis_test.go +++ b/x/genutil/client/cli/validate_genesis_test.go @@ -62,7 +62,7 @@ func TestValidateGenesis(t *testing.T) { }(), "section is missing in the app_state", module.NewManagerFromMap(map[string]appmodulev2.AppModule{ - "custommod": staking.NewAppModule(cdc, nil, nil, nil), + "custommod": staking.NewAppModule(cdc, nil), }), }, { diff --git a/x/gov/go.mod b/x/gov/go.mod index cf006f093661..a2a6714387cf 100644 --- a/x/gov/go.mod +++ b/x/gov/go.mod @@ -30,9 +30,10 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 google.golang.org/grpc v1.66.2 google.golang.org/protobuf v1.34.2 - gotest.tools/v3 v3.5.1 ) +require gotest.tools/v3 v3.5.1 // indirect + require ( buf.build/gen/go/cometbft/cometbft/protocolbuffers/go v1.34.2-20240701160653-fedbb9acfd2f.2 // indirect buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.34.2-20240130113600-88ef6483f90f.2 // indirect diff --git a/x/gov/module.go b/x/gov/module.go index 01a66ef07b1d..8fc5f3061aab 100644 --- a/x/gov/module.go +++ b/x/gov/module.go @@ -21,6 +21,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simsx" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" @@ -209,27 +210,32 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { simulation.RandomizedGenState(simState) } -// ProposalContents returns all the gov content functions used to -// simulate governance proposals. -func (AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { //nolint:staticcheck // used for legacy testing - return simulation.ProposalContents() -} - -// ProposalMsgs returns all the gov msgs used to simulate governance proposals. -func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { - return simulation.ProposalMsgs() -} - // RegisterStoreDecoder registers a decoder for gov module's types func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[govtypes.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.keeper.Schema) } -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return simulation.WeightedOperations( - simState.AppParams, simState.TxConfig, - am.accountKeeper, am.bankKeeper, am.keeper, - simState.ProposalMsgs, simState.LegacyProposalContents, - ) +// ProposalMsgsX returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("submit_text_proposal", 5), simulation.TextProposalFactory()) +} + +func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Registry, proposalMsgIter simsx.WeightedProposalMsgIter, + legacyProposals []simtypes.WeightedProposalContent, //nolint:staticcheck // used for legacy proposal types +) { + // submit proposal for each payload message + for weight, factory := range proposalMsgIter { + // use a ratio so that we don't flood with gov ops + reg.Add(weight/25, simulation.MsgSubmitProposalFactory(am.keeper, factory)) + } + for _, wContent := range legacyProposals { + reg.Add(weights.Get(wContent.AppParamsKey(), uint32(wContent.DefaultWeight())), simulation.MsgSubmitLegacyProposalFactory(am.keeper, wContent.ContentSimulatorFn())) + } + + state := simulation.NewSharedState() + reg.Add(weights.Get("msg_deposit", 100), simulation.MsgDepositFactory(am.keeper, state)) + reg.Add(weights.Get("msg_vote", 67), simulation.MsgVoteFactory(am.keeper, state)) + reg.Add(weights.Get("msg_weighted_vote", 33), simulation.MsgWeightedVoteFactory(am.keeper, state)) + reg.Add(weights.Get("cancel_proposal", 5), simulation.MsgCancelProposalFactory(am.keeper, state)) + reg.Add(weights.Get("legacy_text_proposal", 5), simulation.MsgSubmitLegacyProposalFactory(am.keeper, simulation.SimulateLegacyTextProposalContent)) } diff --git a/x/gov/simulation/genesis.go b/x/gov/simulation/genesis.go index a522593a2b96..26f7bdafa6f9 100644 --- a/x/gov/simulation/genesis.go +++ b/x/gov/simulation/genesis.go @@ -1,8 +1,6 @@ package simulation import ( - "encoding/json" - "fmt" "math/rand" "time" @@ -198,10 +196,5 @@ func RandomizedGenState(simState *module.SimulationState) { ), ) - bz, err := json.MarshalIndent(&govGenesis, "", " ") - if err != nil { - panic(err) - } - fmt.Printf("Selected randomly generated governance parameters:\n%s\n", bz) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(govGenesis) } diff --git a/x/gov/simulation/msg_factory.go b/x/gov/simulation/msg_factory.go new file mode 100644 index 000000000000..a880288e9161 --- /dev/null +++ b/x/gov/simulation/msg_factory.go @@ -0,0 +1,371 @@ +package simulation + +import ( + "context" + "math" + "math/rand" + "sync/atomic" + "time" + + sdkmath "cosmossdk.io/math" + "cosmossdk.io/x/gov/keeper" + v1 "cosmossdk.io/x/gov/types/v1" + + "github.com/cosmos/cosmos-sdk/simsx" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +func MsgDepositFactory(k *keeper.Keeper, sharedState *SharedState) simsx.SimMsgFactoryFn[*v1.MsgDeposit] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *v1.MsgDeposit) { + r := testData.Rand() + proposalID, ok := randomProposalID(r, k, ctx, v1.StatusDepositPeriod, sharedState) + if !ok { + reporter.Skip("no proposal in deposit state") + return nil, nil + } + proposal, err := k.Proposals.Get(ctx, proposalID) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + // calculate deposit amount + deposit := randDeposit(ctx, proposal, k, r, reporter) + if reporter.IsSkipped() { + return nil, nil + } + from := testData.AnyAccount(reporter, simsx.WithLiquidBalanceGTE(deposit)) + return []simsx.SimAccount{from}, v1.NewMsgDeposit(from.AddressBech32, proposalID, sdk.NewCoins(deposit)) + } +} + +func MsgVoteFactory(k *keeper.Keeper, sharedState *SharedState) simsx.SimMsgFactoryFn[*v1.MsgVote] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *v1.MsgVote) { + r := testData.Rand() + proposalID, ok := randomProposalID(r, k, ctx, v1.StatusVotingPeriod, sharedState) + if !ok { + reporter.Skip("no proposal in deposit state") + return nil, nil + } + from := testData.AnyAccount(reporter, simsx.WithSpendableBalance()) + msg := v1.NewMsgVote(from.AddressBech32, proposalID, randomVotingOption(r.Rand), "") + return []simsx.SimAccount{from}, msg + } +} + +func MsgWeightedVoteFactory(k *keeper.Keeper, sharedState *SharedState) simsx.SimMsgFactoryFn[*v1.MsgVoteWeighted] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *v1.MsgVoteWeighted) { + r := testData.Rand() + proposalID, ok := randomProposalID(r, k, ctx, v1.StatusVotingPeriod, sharedState) + if !ok { + reporter.Skip("no proposal in deposit state") + return nil, nil + } + from := testData.AnyAccount(reporter, simsx.WithSpendableBalance()) + msg := v1.NewMsgVoteWeighted(from.AddressBech32, proposalID, randomWeightedVotingOptions(r.Rand), "") + return []simsx.SimAccount{from}, msg + } +} + +func MsgCancelProposalFactory(k *keeper.Keeper, sharedState *SharedState) simsx.SimMsgFactoryFn[*v1.MsgCancelProposal] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *v1.MsgCancelProposal) { + r := testData.Rand() + status := simsx.OneOf(r, []v1.ProposalStatus{v1.StatusDepositPeriod, v1.StatusVotingPeriod}) + proposalID, ok := randomProposalID(r, k, ctx, status, sharedState) + if !ok { + reporter.Skip("no proposal in deposit state") + return nil, nil + } + proposal, err := k.Proposals.Get(ctx, proposalID) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + // is cancellable? copied from keeper + maxCancelPeriodRate := sdkmath.LegacyMustNewDecFromStr(must(k.Params.Get(ctx)).ProposalCancelMaxPeriod) + maxCancelPeriod := time.Duration(float64(proposal.VotingEndTime.Sub(*proposal.VotingStartTime)) * maxCancelPeriodRate.MustFloat64()).Round(time.Second) + if proposal.VotingEndTime.Add(-maxCancelPeriod).Before(simsx.BlockTime(ctx)) { + reporter.Skip("not cancellable anymore") + return nil, nil + } + + from := testData.GetAccount(reporter, proposal.Proposer) + if from.LiquidBalance().Empty() { + reporter.Skip("proposer is broke") + return nil, nil + } + msg := v1.NewMsgCancelProposal(proposalID, from.AddressBech32) + return []simsx.SimAccount{from}, msg + } +} + +func MsgSubmitLegacyProposalFactory(k *keeper.Keeper, contentSimFn simtypes.ContentSimulatorFn) simsx.SimMsgFactoryX { //nolint:staticcheck // used for legacy testing + return simsx.NewSimMsgFactoryWithFutureOps[*v1.MsgSubmitProposal](func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter, fOpsReg simsx.FutureOpsRegistry) ([]simsx.SimAccount, *v1.MsgSubmitProposal) { + // 1) submit proposal now + accs := testData.AllAccounts() + content := contentSimFn(testData.Rand().Rand, ctx, accs) + if content == nil { + reporter.Skip("content is nil") + return nil, nil + } + govacc := must(testData.AddressCodec().BytesToString(k.GetGovernanceAccount(ctx).GetAddress())) + contentMsg := must(v1.NewLegacyContent(content, govacc)) + return submitProposalWithVotesScheduled(ctx, k, testData, reporter, fOpsReg, contentMsg) + }) +} + +func MsgSubmitProposalFactory(k *keeper.Keeper, payloadFactory simsx.SimMsgFactoryX) simsx.SimMsgFactoryX { + return simsx.NewSimMsgFactoryWithFutureOps[*v1.MsgSubmitProposal](func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter, fOpsReg simsx.FutureOpsRegistry) ([]simsx.SimAccount, *v1.MsgSubmitProposal) { + _, proposalMsg := payloadFactory.Create()(ctx, testData, reporter) + return submitProposalWithVotesScheduled(ctx, k, testData, reporter, fOpsReg, proposalMsg) + }) +} + +func submitProposalWithVotesScheduled( + ctx context.Context, + k *keeper.Keeper, + testData *simsx.ChainDataSource, + reporter simsx.SimulationReporter, + fOpsReg simsx.FutureOpsRegistry, + proposalMsgs ...sdk.Msg, +) ([]simsx.SimAccount, *v1.MsgSubmitProposal) { + r := testData.Rand() + expedited := true + // expedited := r.Bool() + params := must(k.Params.Get(ctx)) + minDeposits := params.MinDeposit + if expedited { + minDeposits = params.ExpeditedMinDeposit + } + minDeposit := r.Coin(minDeposits) + + minDepositRatio := must(sdkmath.LegacyNewDecFromStr(params.GetMinDepositRatio())) + threshold := minDeposit.Amount.ToLegacyDec().Mul(minDepositRatio).TruncateInt() + + minDepositPercent := must(sdkmath.LegacyNewDecFromStr(params.MinInitialDepositRatio)) + minAmount := sdkmath.LegacyNewDecFromInt(minDeposit.Amount).Mul(minDepositPercent).TruncateInt() + amount, err := r.PositiveSDKIntn(minDeposit.Amount.Sub(minAmount)) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + if amount.LT(threshold) { + reporter.Skip("below threshold amount for proposal") + return nil, nil + } + deposit := minDeposit + // deposit := sdk.Coin{Amount: amount.Add(minAmount), Denom: minDeposit.Denom} + + proposer := testData.AnyAccount(reporter, simsx.WithLiquidBalanceGTE(deposit)) + if reporter.IsSkipped() || !proposer.LiquidBalance().BlockAmount(deposit) { + return nil, nil + } + proposalType := v1.ProposalType_PROPOSAL_TYPE_STANDARD + if expedited { + proposalType = v1.ProposalType_PROPOSAL_TYPE_EXPEDITED + } + msg, err := v1.NewMsgSubmitProposal( + proposalMsgs, + sdk.Coins{deposit}, + proposer.AddressBech32, + r.StringN(100), + r.StringN(100), + r.StringN(100), + proposalType, + ) + if err != nil { + reporter.Skip("unable to generate a submit proposal msg") + return nil, nil + } + // futureOps + var ( + // The states are: + // column 1: All validators vote + // column 2: 90% vote + // column 3: 75% vote + // column 4: 40% vote + // column 5: 15% vote + // column 6: no one votes + // All columns sum to 100 for simplicity, values chosen by @valardragon semi-arbitrarily, + // feel free to change. + numVotesTransitionMatrix = must(simulation.CreateTransitionMatrix([][]int{ + {20, 10, 0, 0, 0, 0}, + {55, 50, 20, 10, 0, 0}, + {25, 25, 30, 25, 30, 15}, + {0, 15, 30, 25, 30, 30}, + {0, 0, 20, 30, 30, 30}, + {0, 0, 0, 10, 10, 25}, + })) + statePercentageArray = []float64{1, .9, .75, .4, .15, 0} + curNumVotesState = 1 + ) + + // get the submitted proposal ID + proposalID := must(k.ProposalID.Peek(ctx)) + + // 2) Schedule operations for votes + // 2.1) first pick a number of people to vote. + curNumVotesState = numVotesTransitionMatrix.NextState(r.Rand, curNumVotesState) + numVotes := int(math.Ceil(float64(testData.AccountsCount()) * statePercentageArray[curNumVotesState])) + + // 2.2) select who votes and when + whoVotes := r.Perm(testData.AccountsCount()) + + // didntVote := whoVotes[numVotes:] + whoVotes = whoVotes[:numVotes] + votingPeriod := params.VotingPeriod + // future ops so that votes do not flood the sims. + if r.Intn(100) == 1 { // 1% chance + now := simsx.BlockTime(ctx) + for i := 0; i < numVotes; i++ { + var vF simsx.SimMsgFactoryFn[*v1.MsgVote] = func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *v1.MsgVote) { + switch p, err := k.Proposals.Get(ctx, proposalID); { + case err != nil: + reporter.Skip(err.Error()) + return nil, nil + case p.Status != v1.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD: + reporter.Skip("proposal not in voting period") + return nil, nil + } + voter := testData.AccountAt(reporter, whoVotes[i]) + msg := v1.NewMsgVote(voter.AddressBech32, proposalID, randomVotingOption(r.Rand), "") + return []simsx.SimAccount{voter}, msg + } + whenVote := now.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second) + fOpsReg.Add(whenVote, vF) + } + } + return []simsx.SimAccount{proposer}, msg +} + +// TextProposalFactory returns a random text proposal content. +// A text proposal is a proposal that contains no msgs. +func TextProposalFactory() simsx.SimMsgFactoryFn[sdk.Msg] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, sdk.Msg) { + return nil, nil + } +} + +func randDeposit(ctx context.Context, proposal v1.Proposal, k *keeper.Keeper, r *simsx.XRand, reporter simsx.SimulationReporter) sdk.Coin { + params, err := k.Params.Get(ctx) + if err != nil { + reporter.Skipf("gov params: %s", err) + return sdk.Coin{} + } + minDeposits := params.MinDeposit + if proposal.ProposalType == v1.ProposalType_PROPOSAL_TYPE_EXPEDITED { + minDeposits = params.ExpeditedMinDeposit + } + minDeposit := simsx.OneOf(r, minDeposits) + minDepositRatio, err := sdkmath.LegacyNewDecFromStr(params.GetMinDepositRatio()) + if err != nil { + reporter.Skip(err.Error()) + return sdk.Coin{} + } + + threshold := minDeposit.Amount.ToLegacyDec().Mul(minDepositRatio).TruncateInt() + depositAmount, err := r.PositiveSDKIntInRange(threshold, minDeposit.Amount) + if err != nil { + reporter.Skipf("deposit amount: %s", err) + return sdk.Coin{} + } + return sdk.Coin{Denom: minDeposit.Denom, Amount: depositAmount} +} + +// Pick a random proposal ID between the initial proposal ID +// (defined in gov GenesisState) and the latest proposal ID +// that matches a given Status. +// It does not provide a default ID. +func randomProposalID(r *simsx.XRand, k *keeper.Keeper, ctx context.Context, status v1.ProposalStatus, s *SharedState) (proposalID uint64, found bool) { + proposalID, _ = k.ProposalID.Peek(ctx) + if initialProposalID := s.getMinProposalID(); initialProposalID == unsetProposalID { + s.setMinProposalID(proposalID) + } else if initialProposalID < proposalID { + proposalID = r.Uint64InRange(initialProposalID, proposalID) + } + proposal, err := k.Proposals.Get(ctx, proposalID) + if err != nil || proposal.Status != status { + return proposalID, false + } + + return proposalID, true +} + +// Pick a random weighted voting options +func randomWeightedVotingOptions(r *rand.Rand) v1.WeightedVoteOptions { + w1 := r.Intn(100 + 1) + w2 := r.Intn(100 - w1 + 1) + w3 := r.Intn(100 - w1 - w2 + 1) + w4 := 100 - w1 - w2 - w3 + weightedVoteOptions := v1.WeightedVoteOptions{} + if w1 > 0 { + weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{ + Option: v1.OptionYes, + Weight: sdkmath.LegacyNewDecWithPrec(int64(w1), 2).String(), + }) + } + if w2 > 0 { + weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{ + Option: v1.OptionAbstain, + Weight: sdkmath.LegacyNewDecWithPrec(int64(w2), 2).String(), + }) + } + if w3 > 0 { + weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{ + Option: v1.OptionNo, + Weight: sdkmath.LegacyNewDecWithPrec(int64(w3), 2).String(), + }) + } + if w4 > 0 { + weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{ + Option: v1.OptionNoWithVeto, + Weight: sdkmath.LegacyNewDecWithPrec(int64(w4), 2).String(), + }) + } + return weightedVoteOptions +} + +func randomVotingOption(r *rand.Rand) v1.VoteOption { + switch r.Intn(4) { + case 0: + return v1.OptionYes + case 1: + return v1.OptionAbstain + case 2: + return v1.OptionNo + case 3: + return v1.OptionNoWithVeto + default: + panic("invalid vote option") + } +} + +func must[T any](r T, err error) T { + if err != nil { + panic(err) + } + return r +} + +const unsetProposalID = 100000000000000 + +// SharedState shared state between message invocations +type SharedState struct { + minProposalID atomic.Uint64 +} + +// NewSharedState constructor +func NewSharedState() *SharedState { + r := &SharedState{} + r.setMinProposalID(unsetProposalID) + return r +} + +func (s *SharedState) getMinProposalID() uint64 { + return s.minProposalID.Load() +} + +func (s *SharedState) setMinProposalID(id uint64) { + s.minProposalID.Store(id) +} diff --git a/x/gov/simulation/operations.go b/x/gov/simulation/operations.go deleted file mode 100644 index a2749acb8cb3..000000000000 --- a/x/gov/simulation/operations.go +++ /dev/null @@ -1,752 +0,0 @@ -package simulation - -import ( - "math" - "math/rand" - "sync/atomic" - "time" - - sdkmath "cosmossdk.io/math" - "cosmossdk.io/x/gov/keeper" - "cosmossdk.io/x/gov/types" - v1 "cosmossdk.io/x/gov/types/v1" - - "github.com/cosmos/cosmos-sdk/client" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -const unsetProposalID = 100000000000000 - -// Governance message types and routes -var ( - TypeMsgDeposit = sdk.MsgTypeURL(&v1.MsgDeposit{}) - TypeMsgVote = sdk.MsgTypeURL(&v1.MsgVote{}) - TypeMsgVoteWeighted = sdk.MsgTypeURL(&v1.MsgVoteWeighted{}) - TypeMsgSubmitProposal = sdk.MsgTypeURL(&v1.MsgSubmitProposal{}) - TypeMsgCancelProposal = sdk.MsgTypeURL(&v1.MsgCancelProposal{}) -) - -// Simulation operation weights constants -const ( - OpWeightMsgDeposit = "op_weight_msg_deposit" - OpWeightMsgVote = "op_weight_msg_vote" - OpWeightMsgVoteWeighted = "op_weight_msg_weighted_vote" - OpWeightMsgCancelProposal = "op_weight_msg_cancel_proposal" - - DefaultWeightMsgDeposit = 100 - DefaultWeightMsgVote = 67 - DefaultWeightMsgVoteWeighted = 33 - DefaultWeightTextProposal = 5 - DefaultWeightMsgCancelProposal = 5 -) - -// SharedState shared state between message invocations -type SharedState struct { - minProposalID atomic.Uint64 -} - -// NewSharedState constructor -func NewSharedState() *SharedState { - r := &SharedState{} - r.setMinProposalID(unsetProposalID) - return r -} - -func (s *SharedState) getMinProposalID() uint64 { - return s.minProposalID.Load() -} - -func (s *SharedState) setMinProposalID(id uint64) { - s.minProposalID.Store(id) -} - -// WeightedOperations returns all the operations from the module with their respective weights -func WeightedOperations( - appParams simtypes.AppParams, - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, - wMsgs []simtypes.WeightedProposalMsg, - wContents []simtypes.WeightedProposalContent, //nolint:staticcheck // used for legacy testing -) simulation.WeightedOperations { - var ( - weightMsgDeposit int - weightMsgVote int - weightMsgVoteWeighted int - weightMsgCancelProposal int - ) - - appParams.GetOrGenerate(OpWeightMsgDeposit, &weightMsgDeposit, nil, - func(_ *rand.Rand) { - weightMsgDeposit = DefaultWeightMsgDeposit - }, - ) - - appParams.GetOrGenerate(OpWeightMsgVote, &weightMsgVote, nil, - func(_ *rand.Rand) { - weightMsgVote = DefaultWeightMsgVote - }, - ) - - appParams.GetOrGenerate(OpWeightMsgVoteWeighted, &weightMsgVoteWeighted, nil, - func(_ *rand.Rand) { - weightMsgVoteWeighted = DefaultWeightMsgVoteWeighted - }, - ) - - appParams.GetOrGenerate(OpWeightMsgCancelProposal, &weightMsgCancelProposal, nil, - func(_ *rand.Rand) { - weightMsgCancelProposal = DefaultWeightMsgCancelProposal - }, - ) - - // generate the weighted operations for the proposal msgs - var wProposalOps simulation.WeightedOperations - for _, wMsg := range wMsgs { - wMsg := wMsg // pin variable - var weight int - appParams.GetOrGenerate(wMsg.AppParamsKey(), &weight, nil, - func(_ *rand.Rand) { weight = wMsg.DefaultWeight() }, - ) - - wProposalOps = append( - wProposalOps, - simulation.NewWeightedOperation( - weight, - SimulateMsgSubmitProposal(txGen, ak, bk, k, wMsg.MsgSimulatorFn()), - ), - ) - } - - // generate the weighted operations for the proposal contents - var wLegacyProposalOps simulation.WeightedOperations - for _, wContent := range wContents { - wContent := wContent // pin variable - var weight int - appParams.GetOrGenerate(wContent.AppParamsKey(), &weight, nil, - func(_ *rand.Rand) { weight = wContent.DefaultWeight() }, - ) - - wLegacyProposalOps = append( - wLegacyProposalOps, - simulation.NewWeightedOperation( - weight, - SimulateMsgSubmitLegacyProposal(txGen, ak, bk, k, wContent.ContentSimulatorFn()), - ), - ) - } - state := NewSharedState() - wGovOps := simulation.WeightedOperations{ - simulation.NewWeightedOperation( - weightMsgDeposit, - SimulateMsgDeposit(txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgVote, - SimulateMsgVote(txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgVoteWeighted, - SimulateMsgVoteWeighted(txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgCancelProposal, - SimulateMsgCancelProposal(txGen, ak, bk, k), - ), - } - - return append(wGovOps, append(wProposalOps, wLegacyProposalOps...)...) -} - -// SimulateMsgSubmitProposal simulates creating a msg Submit Proposal -// voting on the proposal, and subsequently slashing the proposal. It is implemented using -// future operations. -func SimulateMsgSubmitProposal( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, - msgSim simtypes.MsgSimulatorFnX, -) simtypes.Operation { - return func(r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgs := []sdk.Msg{} - proposalMsg, err := msgSim(ctx, r, accs, ak.AddressCodec()) - if err != nil { - return simtypes.OperationMsg{}, nil, err - } - if proposalMsg != nil { - msgs = append(msgs, proposalMsg) - } - - return simulateMsgSubmitProposal(txGen, ak, bk, k, msgs)(r, app, ctx, accs, chainID) - } -} - -// SimulateMsgSubmitLegacyProposal simulates creating a msg Submit Proposal -// voting on the proposal, and subsequently slashing the proposal. It is implemented using -// future operations. -func SimulateMsgSubmitLegacyProposal( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, - contentSim simtypes.ContentSimulatorFn, //nolint:staticcheck // used for legacy testing -) simtypes.Operation { - return func(r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - // 1) submit proposal now - content := contentSim(r, ctx, accs) - if content == nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "content is nil"), nil, nil - } - - govacc, err := ak.AddressCodec().BytesToString(k.GetGovernanceAccount(ctx).GetAddress()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "error getting governance account address"), nil, err - } - contentMsg, err := v1.NewLegacyContent(content, govacc) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "error converting legacy content into proposal message"), nil, err - } - - return simulateMsgSubmitProposal(txGen, ak, bk, k, []sdk.Msg{contentMsg})(r, app, ctx, accs, chainID) - } -} - -func simulateMsgSubmitProposal( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, - proposalMsgs []sdk.Msg, -) simtypes.Operation { - // The states are: - // column 1: All validators vote - // column 2: 90% vote - // column 3: 75% vote - // column 4: 40% vote - // column 5: 15% vote - // column 6: no one votes - // All columns sum to 100 for simplicity, values chosen by @valardragon semi-arbitrarily, - // feel free to change. - numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ - {20, 10, 0, 0, 0, 0}, - {55, 50, 20, 10, 0, 0}, - {25, 25, 30, 25, 30, 15}, - {0, 15, 30, 25, 30, 30}, - {0, 0, 20, 30, 30, 30}, - {0, 0, 0, 10, 10, 25}, - }) - - statePercentageArray := []float64{1, .9, .75, .4, .15, 0} - curNumVotesState := 1 - - return func( - r *rand.Rand, - app simtypes.AppEntrypoint, - ctx sdk.Context, - accs []simtypes.Account, - chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - simAccount, _ := simtypes.RandomAcc(r, accs) - expedited := r.Intn(2) == 0 - deposit, skip, err := randomDeposit(r, ctx, ak, bk, k, simAccount.Address, true, expedited) - switch { - case skip: - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "skip deposit"), nil, nil - case err != nil: - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "unable to generate deposit"), nil, err - } - - proposalType := v1.ProposalType_PROPOSAL_TYPE_STANDARD - if expedited { - proposalType = v1.ProposalType_PROPOSAL_TYPE_EXPEDITED - } - - accAddr, err := ak.AddressCodec().BytesToString(simAccount.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "error getting simAccount address"), nil, err - } - msg, err := v1.NewMsgSubmitProposal( - proposalMsgs, - deposit, - accAddr, - simtypes.RandStringOfLength(r, 100), - simtypes.RandStringOfLength(r, 100), - simtypes.RandStringOfLength(r, 100), - proposalType, - ) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate a submit proposal msg"), nil, err - } - - account := ak.GetAccount(ctx, simAccount.Address) - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - simAccount.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - opMsg := simtypes.NewOperationMsg(msg, true, "") - - // get the submitted proposal ID - proposalID, err := k.ProposalID.Peek(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate proposalID"), nil, err - } - - // 2) Schedule operations for votes - // 2.1) first pick a number of people to vote. - curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState) - numVotes := int(math.Ceil(float64(len(accs)) * statePercentageArray[curNumVotesState])) - - // 2.2) select who votes and when - whoVotes := r.Perm(len(accs)) - - // didntVote := whoVotes[numVotes:] - whoVotes = whoVotes[:numVotes] - params, _ := k.Params.Get(ctx) - votingPeriod := params.VotingPeriod - var fops []simtypes.FutureOperation - if false { // future ops deactivated because they were not implemented correct in the framework before and flood the system now - fops = make([]simtypes.FutureOperation, numVotes+1) - for i := 0; i < numVotes; i++ { - whenVote := ctx.HeaderInfo().Time.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second) - fops[i] = simtypes.FutureOperation{ - BlockTime: whenVote, - Op: func(r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accounts []simtypes.Account, chainID string) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { - return operationSimulateMsgVote(txGen, ak, bk, k, accs[whoVotes[i]], int64(proposalID), nil)(r, app, ctx, accounts, chainID) - }, - } - } - } - return opMsg, fops, nil - } -} - -// SimulateMsgDeposit generates a MsgDeposit with random values. -func SimulateMsgDeposit( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, - accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - simAccount, _ := simtypes.RandomAcc(r, accs) - proposalID, ok := randomProposalID(r, k, ctx, v1.StatusDepositPeriod, s) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "unable to generate proposalID"), nil, nil - } - - p, err := k.Proposals.Get(ctx, proposalID) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "unable to get proposal"), nil, err - } - - isExpedited := p.ProposalType == v1.ProposalType_PROPOSAL_TYPE_EXPEDITED - - deposit, skip, err := randomDeposit(r, ctx, ak, bk, k, simAccount.Address, false, isExpedited) - switch { - case skip: - return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "skip deposit"), nil, nil - case err != nil: - return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "unable to generate deposit"), nil, err - } - - addr, err := ak.AddressCodec().BytesToString(simAccount.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "unable to get simAccount address"), nil, err - } - msg := v1.NewMsgDeposit(addr, proposalID, deposit) - - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - var fees sdk.Coins - coins, hasNeg := spendable.SafeSub(deposit...) - if !hasNeg { - fees, err = simtypes.RandomFees(r, coins) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate fees"), nil, err - } - } - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txGen, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - ModuleName: types.ModuleName, - } - - return simulation.GenAndDeliverTx(txCtx, fees) - } -} - -// SimulateMsgVote generates a MsgVote with random values. -func SimulateMsgVote( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return operationSimulateMsgVote(txGen, ak, bk, k, simtypes.Account{}, -1, s) -} - -func operationSimulateMsgVote( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, - simAccount simtypes.Account, - proposalIDInt int64, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, - accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - if simAccount.Equals(simtypes.Account{}) { - simAccount, _ = simtypes.RandomAcc(r, accs) - } - - var proposalID uint64 - - switch { - case proposalIDInt < 0: - var ok bool - proposalID, ok = randomProposalID(r, k, ctx, v1.StatusVotingPeriod, s) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgVote, "unable to generate proposalID"), nil, nil - } - default: - proposalID = uint64(proposalIDInt) - } - - option := randomVotingOption(r) - addr, err := ak.AddressCodec().BytesToString(simAccount.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgVote, "unable to get simAccount address"), nil, err - } - msg := v1.NewMsgVote(addr, proposalID, option, "") - - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txGen, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// SimulateMsgVoteWeighted generates a MsgVoteWeighted with random values. -func SimulateMsgVoteWeighted( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return operationSimulateMsgVoteWeighted(txGen, ak, bk, k, simtypes.Account{}, -1, s) -} - -func operationSimulateMsgVoteWeighted( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, - simAccount simtypes.Account, - proposalIDInt int64, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, - accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - if simAccount.Equals(simtypes.Account{}) { - simAccount, _ = simtypes.RandomAcc(r, accs) - } - - var proposalID uint64 - - switch { - case proposalIDInt < 0: - var ok bool - proposalID, ok = randomProposalID(r, k, ctx, v1.StatusVotingPeriod, s) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgVoteWeighted, "unable to generate proposalID"), nil, nil - } - default: - proposalID = uint64(proposalIDInt) - } - - options := randomWeightedVotingOptions(r) - addr, err := ak.AddressCodec().BytesToString(simAccount.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgVoteWeighted, "unable to get simAccount address"), nil, err - } - msg := v1.NewMsgVoteWeighted(addr, proposalID, options, "") - - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txGen, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// SimulateMsgCancelProposal generates a MsgCancelProposal. -func SimulateMsgCancelProposal(txGen client.TxConfig, ak types.AccountKeeper, bk types.BankKeeper, k *keeper.Keeper) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, - accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - simAccount := accs[0] - proposal := randomProposal(r, k, ctx) - if proposal == nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "no proposals found"), nil, nil - } - - proposerAddr, err := ak.AddressCodec().BytesToString(simAccount.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "invalid proposer"), nil, err - } - if proposal.Proposer != proposerAddr { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "invalid proposer"), nil, nil - } - - if (proposal.Status != v1.StatusDepositPeriod) && (proposal.Status != v1.StatusVotingPeriod) { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "invalid proposal status"), nil, nil - } - - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - accAddr, err := ak.AddressCodec().BytesToString(account.GetAddress()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "could not get account address"), nil, err - } - msg := v1.NewMsgCancelProposal(proposal.Id, accAddr) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txGen, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// Pick a random deposit with a random denomination with a -// deposit amount between (0, min(balance, minDepositAmount)) -// This is to simulate multiple users depositing to get the -// proposal above the minimum deposit amount -func randomDeposit( - r *rand.Rand, - ctx sdk.Context, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, - addr sdk.AccAddress, - useMinAmount bool, - expedited bool, -) (deposit sdk.Coins, skip bool, err error) { - account := ak.GetAccount(ctx, addr) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - if spendable.Empty() { - return nil, true, nil // skip - } - - params, _ := k.Params.Get(ctx) - minDeposit := params.MinDeposit - if expedited { - minDeposit = params.ExpeditedMinDeposit - } - denomIndex := r.Intn(len(minDeposit)) - denom := minDeposit[denomIndex].Denom - - spendableBalance := spendable.AmountOf(denom) - if spendableBalance.IsZero() { - return nil, true, nil - } - - minDepositAmount := minDeposit[denomIndex].Amount - - minDepositRatio, err := sdkmath.LegacyNewDecFromStr(params.GetMinDepositRatio()) - if err != nil { - return nil, false, err - } - - threshold := minDepositAmount.ToLegacyDec().Mul(minDepositRatio).TruncateInt() - - minAmount := sdkmath.ZeroInt() - if useMinAmount { - minDepositPercent, err := sdkmath.LegacyNewDecFromStr(params.MinInitialDepositRatio) - if err != nil { - return nil, false, err - } - - minAmount = sdkmath.LegacyNewDecFromInt(minDepositAmount).Mul(minDepositPercent).TruncateInt() - } - - amount, err := simtypes.RandPositiveInt(r, minDepositAmount.Sub(minAmount)) - if err != nil { - return nil, false, err - } - amount = amount.Add(minAmount) - - if amount.GT(spendableBalance) || amount.LT(threshold) { - return nil, true, nil - } - - return sdk.Coins{sdk.NewCoin(denom, amount)}, false, nil -} - -// randomProposal returns a random proposal stored in state -func randomProposal(r *rand.Rand, k *keeper.Keeper, ctx sdk.Context) *v1.Proposal { - var proposals []*v1.Proposal - err := k.Proposals.Walk(ctx, nil, func(key uint64, value v1.Proposal) (stop bool, err error) { - proposals = append(proposals, &value) - return false, nil - }) - if err != nil { - panic(err) - } - if len(proposals) == 0 { - return nil - } - randomIndex := r.Intn(len(proposals)) - return proposals[randomIndex] -} - -// Pick a random proposal ID between the initial proposal ID -// (defined in gov GenesisState) and the latest proposal ID -// that matches a given Status. -// It does not provide a default ID. -func randomProposalID(r *rand.Rand, k *keeper.Keeper, ctx sdk.Context, status v1.ProposalStatus, s *SharedState) (proposalID uint64, found bool) { - proposalID, _ = k.ProposalID.Peek(ctx) - if initialProposalID := s.getMinProposalID(); initialProposalID == unsetProposalID { - s.setMinProposalID(proposalID) - } else if initialProposalID < proposalID { - proposalID = uint64(simtypes.RandIntBetween(r, int(initialProposalID), int(proposalID))) - } - proposal, err := k.Proposals.Get(ctx, proposalID) - if err != nil || proposal.Status != status { - return proposalID, false - } - - return proposalID, true -} - -// Pick a random voting option -func randomVotingOption(r *rand.Rand) v1.VoteOption { - switch r.Intn(4) { - case 0: - return v1.OptionYes - case 1: - return v1.OptionAbstain - case 2: - return v1.OptionNo - case 3: - return v1.OptionNoWithVeto - default: - panic("invalid vote option") - } -} - -// Pick a random weighted voting options -func randomWeightedVotingOptions(r *rand.Rand) v1.WeightedVoteOptions { - w1 := r.Intn(100 + 1) - w2 := r.Intn(100 - w1 + 1) - w3 := r.Intn(100 - w1 - w2 + 1) - w4 := 100 - w1 - w2 - w3 - weightedVoteOptions := v1.WeightedVoteOptions{} - if w1 > 0 { - weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{ - Option: v1.OptionYes, - Weight: sdkmath.LegacyNewDecWithPrec(int64(w1), 2).String(), - }) - } - if w2 > 0 { - weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{ - Option: v1.OptionAbstain, - Weight: sdkmath.LegacyNewDecWithPrec(int64(w2), 2).String(), - }) - } - if w3 > 0 { - weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{ - Option: v1.OptionNo, - Weight: sdkmath.LegacyNewDecWithPrec(int64(w3), 2).String(), - }) - } - if w4 > 0 { - weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{ - Option: v1.OptionNoWithVeto, - Weight: sdkmath.LegacyNewDecWithPrec(int64(w4), 2).String(), - }) - } - return weightedVoteOptions -} diff --git a/x/gov/simulation/proposals.go b/x/gov/simulation/proposals.go index c53d024b13a5..471b51d6ebec 100644 --- a/x/gov/simulation/proposals.go +++ b/x/gov/simulation/proposals.go @@ -4,51 +4,15 @@ import ( "context" "math/rand" - coreaddress "cosmossdk.io/core/address" "cosmossdk.io/x/gov/types/v1beta1" - sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" ) -// OpWeightSubmitTextProposal app params key for text proposal -const OpWeightSubmitTextProposal = "op_weight_submit_text_proposal" - -// ProposalMsgs defines the module weighted proposals' contents -func ProposalMsgs() []simtypes.WeightedProposalMsg { - return []simtypes.WeightedProposalMsg{ - simulation.NewWeightedProposalMsgX( - OpWeightSubmitTextProposal, - DefaultWeightTextProposal, - SimulateTextProposal, - ), - } -} - -// SimulateTextProposal returns a random text proposal content. -// A text proposal is a proposal that contains no msgs. -func SimulateTextProposal(_ context.Context, r *rand.Rand, _ []simtypes.Account, _ coreaddress.Codec) (sdk.Msg, error) { - return nil, nil -} - -// ProposalContents defines the module weighted proposals' contents -// -//nolint:staticcheck // used for legacy testing -func ProposalContents() []simtypes.WeightedProposalContent { - return []simtypes.WeightedProposalContent{ - simulation.NewWeightedProposalContent( - OpWeightMsgDeposit, - DefaultWeightTextProposal, - SimulateLegacyTextProposalContent, - ), - } -} - -// SimulateTextProposalContent returns a random text proposal content. +// SimulateLegacyTextProposalContent returns a random text proposal content. // //nolint:staticcheck // used for legacy testing -func SimulateLegacyTextProposalContent(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) simtypes.Content { +func SimulateLegacyTextProposalContent(r *rand.Rand, _ context.Context, _ []simtypes.Account) simtypes.Content { return v1beta1.NewTextProposal( simtypes.RandStringOfLength(r, 140), simtypes.RandStringOfLength(r, 5000), diff --git a/x/gov/simulation/proposals_test.go b/x/gov/simulation/proposals_test.go deleted file mode 100644 index 939103c75ea9..000000000000 --- a/x/gov/simulation/proposals_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package simulation_test - -import ( - "context" - "math/rand" - "testing" - - "gotest.tools/v3/assert" - - "cosmossdk.io/x/gov/simulation" - - codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -func TestProposalMsgs(t *testing.T) { - // initialize parameters - s := rand.NewSource(1) - r := rand.New(s) - - accounts := simtypes.RandomAccounts(r, 3) - - // execute ProposalMsgs function - weightedProposalMsgs := simulation.ProposalMsgs() - assert.Assert(t, len(weightedProposalMsgs) == 1) - - w0 := weightedProposalMsgs[0] - - // tests w0 interface: - assert.Equal(t, simulation.OpWeightSubmitTextProposal, w0.AppParamsKey()) - assert.Equal(t, simulation.DefaultWeightTextProposal, w0.DefaultWeight()) - - msg, err := w0.MsgSimulatorFn()(context.Background(), r, accounts, codectestutil.CodecOptions{}.GetAddressCodec()) - assert.NilError(t, err) - assert.Assert(t, msg == nil) -} - -func TestProposalContents(t *testing.T) { - // initialize parameters - s := rand.NewSource(1) - r := rand.New(s) - - ctx := sdk.NewContext(nil, true, nil) - accounts := simtypes.RandomAccounts(r, 3) - - // execute ProposalContents function - weightedProposalContent := simulation.ProposalContents() - assert.Assert(t, len(weightedProposalContent) == 1) - - w0 := weightedProposalContent[0] - - // tests w0 interface: - assert.Equal(t, simulation.OpWeightMsgDeposit, w0.AppParamsKey()) - assert.Equal(t, simulation.DefaultWeightTextProposal, w0.DefaultWeight()) - - content := w0.ContentSimulatorFn()(r, ctx, accounts) - - assert.Equal(t, "NxImpptHBIFDQfnxaTiOBJUgNzvqHbVcVJYlIFWFlzFqqRTTyFzDUMntPzyRamUFqeJAEaSHIuUHZoTWDjWXsYxYvwXwXZEsjRQKgKMselyUqWXMbHzRNDHnMzhWSirUgVggjiBxtWDfhzPDgrorEoNmDEiDdBldYegphCBTYWrmFFXNjxhtygsGBFHTejaKjMsqNdikEzDalEyWRHfJhKqifCKsedVuuJbQMbmRVuIPDluAWGpngjgBjOxuRFwSadayHNIhVVmNWBbfaTOldclxTTLUMvaBnLfwjHTtsKetEIvgrxLijhKJNablmvqpWIWsmhWQAYNLycREypoASHnyKWrxpoNLBJuyCGysZJgXbQAAmSIbGxMFXuwMVGZgBiZWfPWorAfjBeekCFvljHAtVZaTOsRxbPIioNxLTnWUTzGTvaNhplQQPmMADRRDuUIsiBpnGqPheKmLnopieVseFdTSAvOCacxaqFWFuXzsrVZzlGfeRpClwKuGEBujaPrzSLjVIOMvLlWxuznEOXlxbZroBRVEvEfBBAHOECribZNrYiFnzQqQmBnLksmFNAadusWAGltuqYNntgOlgOGwSdDjWdLboWyAWIcCfmpGJTfbljKPriLehwObuszICkaXNUkmeddeeRulbZBXJVLgteiKIfofGdNBregwUPlINQECatDSNXSIuefyMxxoKfcmjHEwbVtFiXtEnLJkLHUghmzFiymrgBChucZgOQUpGGVQEpRtIQjIBxYhtZPgUORdxXNWUMErWrUeriqYJPcgIDgLMWAyuuQnsHncCtjvHmvFbzYErxeunQllYDUVlXaRBveRUKeXwEGJFTSAqZtaBSDGDtzlADCnGjuTmYMJlapRsWfugmjwKEuoXJVpZvlcHeFvVvRRktRVGwzLfKezPEMABZtbLExQIjynSoahmkmoTHefdzFoBHMcQHFkKVHhpNtudPqJrYuQswzFuFHbSmpNltFnYJpvMrAYHFrNouZaanEUGHvbHIUUFTCtZrcpRHwgjblxlDNJWzHdBNpAXKJPHWQdrGYcAHSctgVlqwqHoLfHsXUdStwfefwzqLuKEhmMyYLdbZrcPgYqjNHxPexsruwEGStAneKbWkQDDIlCWBLSiAASNhZqNFlPtfqPJoxKsgMdzjWqLWdqKQuJqWPMvwPQWZUtVMOTMYKJbfdlZsjdsomuScvDmbDkgRualsxDvRJuCAmPOXitIbcyWsKGSdrEunFAOdmXnsuyFVgJqEjbklvmwrUlsxjRSfKZxGcpayDdgoFcnVSutxjRgOSFzPwidAjubMncNweqpbxhXGchpZUxuFDOtpnhNUycJICRYqsPhPSCjPTWZFLkstHWJxvdPEAyEIxXgLwbNOjrgzmaujiBABBIXvcXpLrbcEWNNQsbjvgJFgJkflpRohHUutvnaUqoopuKjTDaemDeSdqbnOzcfJpcTuAQtZoiLZOoAIlboFDAeGmSNwkvObPRvRWQgWkGkxwtPauYgdkmypLjbqhlHJIQTntgWjXwZdOyYEdQRRLfMSdnxqppqUofqLbLQDUjwKVKfZJUJQPsWIPwIVaSTrmKskoAhvmZyJgeRpkaTfGgrJzAigcxtfshmiDCFkuiluqtMOkidknnTBtumyJYlIsWLnCQclqdVmikUoMOPdPWwYbJxXyqUVicNxFxyqJTenNblyyKSdlCbiXxUiYUiMwXZASYfvMDPFgxniSjWaZTjHkqlJvtBsXqwPpyVxnJVGFWhfSxgOcduoxkiopJvFjMmFabrGYeVtTXLhxVUEiGwYUvndjFGzDVntUvibiyZhfMQdMhgsiuysLMiePBNXifRLMsSmXPkwlPloUbJveCvUlaalhZHuvdkCnkSHbMbmOnrfEGPwQiACiPlnihiaOdbjPqPiTXaHDoJXjSlZmltGqNHHNrcKdlFSCdmVOuvDcBLdSklyGJmcLTbSFtALdGlPkqqecJrpLCXNPWefoTJNgEJlyMEPneVaxxduAAEqQpHWZodWyRkDAxzyMnFMcjSVqeRXLqsNyNtQBbuRvunZflWSbbvXXdkyLikYqutQhLPONXbvhcQZJPSWnOulqQaXmbfFxAkqfYeseSHOQidHwbcsOaMnSrrmGjjRmEMQNuknupMxJiIeVjmgZvbmjPIQTEhQFULQLBMPrxcFPvBinaOPYWGvYGRKxLZdwamfRQQFngcdSlvwjfaPbURasIsGJVHtcEAxnIIrhSriiXLOlbEBLXFElXJFGxHJczRBIxAuPKtBisjKBwfzZFagdNmjdwIRvwzLkFKWRTDPxJCmpzHUcrPiiXXHnOIlqNVoGSXZewdnCRhuxeYGPVTfrNTQNOxZmxInOazUYNTNDgzsxlgiVEHPKMfbesvPHUqpNkUqbzeuzfdrsuLDpKHMUbBMKczKKWOdYoIXoPYtEjfOnlQLoGnbQUCuERdEFaptwnsHzTJDsuZkKtzMpFaZobynZdzNydEeJJHDYaQcwUxcqvwfWwNUsCiLvkZQiSfzAHftYgAmVsXgtmcYgTqJIawstRYJrZdSxlfRiqTufgEQVambeZZmaAyRQbcmdjVUZZCgqDrSeltJGXPMgZnGDZqISrGDOClxXCxMjmKqEPwKHoOfOeyGmqWqihqjINXLqnyTesZePQRqaWDQNqpLgNrAUKulklmckTijUltQKuWQDwpLmDyxLppPVMwsmBIpOwQttYFMjgJQZLYFPmxWFLIeZihkRNnkzoypBICIxgEuYsVWGIGRbbxqVasYnstWomJnHwmtOhAFSpttRYYzBmyEtZXiCthvKvWszTXDbiJbGXMcrYpKAgvUVFtdKUfvdMfhAryctklUCEdjetjuGNfJjajZtvzdYaqInKtFPPLYmRaXPdQzxdSQfmZDEVHlHGEGNSPRFJuIfKLLfUmnHxHnRjmzQPNlqrXgifUdzAGKVabYqvcDeYoTYgPsBUqehrBhmQUgTvDnsdpuhUoxskDdppTsYMcnDIPSwKIqhXDCIxOuXrywahvVavvHkPuaenjLmEbMgrkrQLHEAwrhHkPRNvonNQKqprqOFVZKAtpRSpvQUxMoXCMZLSSbnLEFsjVfANdQNQVwTmGxqVjVqRuxREAhuaDrFgEZpYKhwWPEKBevBfsOIcaZKyykQafzmGPLRAKDtTcJxJVgiiuUkmyMYuDUNEUhBEdoBLJnamtLmMJQgmLiUELIhLpiEvpOXOvXCPUeldLFqkKOwfacqIaRcnnZvERKRMCKUkMABbDHytQqQblrvoxOZkwzosQfDKGtIdfcXRJNqlBNwOCWoQBcEWyqrMlYZIAXYJmLfnjoJepgSFvrgajaBAIksoyeHqgqbGvpAstMIGmIhRYGGNPRIfOQKsGoKgxtsidhTaAePRCBFqZgPDWCIkqOJezGVkjfYUCZTlInbxBXwUAVRsxHTQtJFnnpmMvXDYCVlEmnZBKhmmxQOIQzxFWpJQkQoSAYzTEiDWEOsVLNrbfzeHFRyeYATakQQWmFDLPbVMCJcWjFGJjfqCoVzlbNNEsqxdSmNPjTjHYOkuEMFLkXYGaoJlraLqayMeCsTjWNRDPBywBJLAPVkGQqTwApVVwYAetlwSbzsdHWsTwSIcctkyKDuRWYDQikRqsKTMJchrliONJeaZIzwPQrNbTwxsGdwuduvibtYndRwpdsvyCktRHFalvUuEKMqXbItfGcNGWsGzubdPMYayOUOINjpcFBeESdwpdlTYmrPsLsVDhpTzoMegKrytNVZkfJRPuDCUXxSlSthOohmsuxmIZUedzxKmowKOdXTMcEtdpHaPWgIsIjrViKrQOCONlSuazmLuCUjLltOGXeNgJKedTVrrVCpWYWHyVrdXpKgNaMJVjbXxnVMSChdWKuZdqpisvrkBJPoURDYxWOtpjzZoOpWzyUuYNhCzRoHsMjmmWDcXzQiHIyjwdhPNwiPqFxeUfMVFQGImhykFgMIlQEoZCaRoqSBXTSWAeDumdbsOGtATwEdZlLfoBKiTvodQBGOEcuATWXfiinSjPmJKcWgQrTVYVrwlyMWhxqNbCMpIQNoSMGTiWfPTCezUjYcdWppnsYJihLQCqbNLRGgqrwHuIvsazapTpoPZIyZyeeSueJuTIhpHMEJfJpScshJubJGfkusuVBgfTWQoywSSliQQSfbvaHKiLnyjdSbpMkdBgXepoSsHnCQaYuHQqZsoEOmJCiuQUpJkmfyfbIShzlZpHFmLCsbknEAkKXKfRTRnuwdBeuOGgFbJLbDksHVapaRayWzwoYBEpmrlAxrUxYMUekKbpjPNfjUCjhbdMAnJmYQVZBQZkFVweHDAlaqJjRqoQPoOMLhyvYCzqEuQsAFoxWrzRnTVjStPadhsESlERnKhpEPsfDxNvxqcOyIulaCkmPdambLHvGhTZzysvqFauEgkFRItPfvisehFmoBhQqmkfbHVsgfHXDPJVyhwPllQpuYLRYvGodxKjkarnSNgsXoKEMlaSKxKdcVgvOkuLcfLFfdtXGTclqfPOfeoVLbqcjcXCUEBgAGplrkgsmIEhWRZLlGPGCwKWRaCKMkBHTAcypUrYjWwCLtOPVygMwMANGoQwFnCqFrUGMCRZUGJKTZIGPyldsifauoMnJPLTcDHmilcmahlqOELaAUYDBuzsVywnDQfwRLGIWozYaOAilMBcObErwgTDNGWnwQMUgFFSKtPDMEoEQCTKVREqrXZSGLqwTMcxHfWotDllNkIJPMbXzjDVjPOOjCFuIvTyhXKLyhUScOXvYthRXpPfKwMhptXaxIxgqBoUqzrWbaoLTVpQoottZyPFfNOoMioXHRuFwMRYUiKvcWPkrayyTLOCFJlAyslDameIuqVAuxErqFPEWIScKpBORIuZqoXlZuTvAjEdlEWDODFRregDTqGNoFBIHxvimmIZwLfFyKUfEWAnNBdtdzDmTPXtpHRGdIbuucfTjOygZsTxPjf", content.GetDescription()) - assert.Equal(t, "XhSUkMhPjMaxKlMIJMOXcnQfyzeOcbWwNbeHVIkPZBSpYuLyYggwexjxusrBqDOTtGTOWeLrQKjLxzIivHSlcxgdXhhuTSkuxKGLwQvuyNhYFmBZHeAerqyNEUzXPFGkqEGqiQWIXnku", content.GetTitle()) - assert.Equal(t, "gov", content.ProposalRoute()) - assert.Equal(t, "Text", content.ProposalType()) -} diff --git a/x/group/keeper/keeper.go b/x/group/keeper/keeper.go index b1fc76f920cc..aab09f065def 100644 --- a/x/group/keeper/keeper.go +++ b/x/group/keeper/keeper.go @@ -231,7 +231,7 @@ func NewKeeper(env appmodule.Environment, cdc codec.Codec, accKeeper group.Accou } // GetGroupSequence returns the current value of the group table sequence -func (k Keeper) GetGroupSequence(ctx sdk.Context) uint64 { +func (k Keeper) GetGroupSequence(ctx context.Context) uint64 { return k.groupTable.Sequence().CurVal(k.KVStoreService.OpenKVStore(ctx)) } diff --git a/x/group/keeper/keeper_test.go b/x/group/keeper/keeper_test.go index b07d1136280d..f761573b5a63 100644 --- a/x/group/keeper/keeper_test.go +++ b/x/group/keeper/keeper_test.go @@ -115,7 +115,7 @@ func (s *TestSuite) SetupTest() { s.Require().NoError(err) s.setNextAccount() - groupSeq := s.groupKeeper.GetGroupSequence(s.sdkCtx) + groupSeq := s.groupKeeper.GetGroupSequence(s.ctx) s.Require().Equal(groupSeq, uint64(1)) policyRes, err := s.groupKeeper.CreateGroupPolicy(s.ctx, policyReq) diff --git a/x/group/keeper/msg_server.go b/x/group/keeper/msg_server.go index 79f449d3095f..5191b2dc3b4d 100644 --- a/x/group/keeper/msg_server.go +++ b/x/group/keeper/msg_server.go @@ -820,12 +820,11 @@ func (k Keeper) doTallyAndUpdate(ctx context.Context, p *group.Proposal, groupIn } // Exec executes the messages from a proposal. -func (k Keeper) Exec(goCtx context.Context, msg *group.MsgExec) (*group.MsgExecResponse, error) { +func (k Keeper) Exec(ctx context.Context, msg *group.MsgExec) (*group.MsgExecResponse, error) { if msg.ProposalId == 0 { return nil, errorsmod.Wrap(errors.ErrEmpty, "proposal id") } - ctx := sdk.UnwrapSDKContext(goCtx) proposal, err := k.getProposal(ctx, msg.ProposalId) if err != nil { return nil, err diff --git a/x/group/keeper/msg_server_test.go b/x/group/keeper/msg_server_test.go index a3e3f9274b43..f345686327d1 100644 --- a/x/group/keeper/msg_server_test.go +++ b/x/group/keeper/msg_server_test.go @@ -3365,7 +3365,7 @@ func (s *TestSuite) TestExecProposalsWhenMemberLeavesOrIsUpdated() { s.setNextAccount() - s.groupKeeper.GetGroupSequence(s.sdkCtx) + s.groupKeeper.GetGroupSequence(s.ctx) policyRes, err := s.groupKeeper.CreateGroupPolicy(s.ctx, policyReq) s.Require().NoError(err) diff --git a/x/group/module/module.go b/x/group/module/module.go index 3f6aecffd827..97b0b40118b7 100644 --- a/x/group/module/module.go +++ b/x/group/module/module.go @@ -19,6 +19,7 @@ import ( sdkclient "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/simsx" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" @@ -160,11 +161,21 @@ func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[group.StoreKey] = simulation.NewDecodeStore(am.cdc) } -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return simulation.WeightedOperations( - am.registry, - simState.AppParams, simState.Cdc, simState.TxConfig, - am.accKeeper, am.bankKeeper, am.keeper, am.cdc, - ) +func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Registry) { + s := simulation.NewSharedState() + // note: using old keys for backwards compatibility + reg.Add(weights.Get("msg_create_group", 100), simulation.MsgCreateGroupFactory()) + reg.Add(weights.Get("msg_update_group_admin", 5), simulation.MsgUpdateGroupAdminFactory(am.keeper, s)) + reg.Add(weights.Get("msg_update_group_metadata", 5), simulation.MsgUpdateGroupMetadataFactory(am.keeper, s)) + reg.Add(weights.Get("msg_update_group_members", 5), simulation.MsgUpdateGroupMembersFactory(am.keeper, s)) + reg.Add(weights.Get("msg_create_group_account", 50), simulation.MsgCreateGroupPolicyFactory(am.keeper, s)) + reg.Add(weights.Get("msg_create_group_with_policy", 50), simulation.MsgCreateGroupWithPolicyFactory()) + reg.Add(weights.Get("msg_update_group_account_admin", 5), simulation.MsgUpdateGroupPolicyAdminFactory(am.keeper, s)) + reg.Add(weights.Get("msg_update_group_account_decision_policy", 5), simulation.MsgUpdateGroupPolicyDecisionPolicyFactory(am.keeper, s)) + reg.Add(weights.Get("msg_update_group_account_metadata", 5), simulation.MsgUpdateGroupPolicyMetadataFactory(am.keeper, s)) + reg.Add(weights.Get("msg_submit_proposal", 2*90), simulation.MsgSubmitProposalFactory(am.keeper, s)) + reg.Add(weights.Get("msg_withdraw_proposal", 20), simulation.MsgWithdrawProposalFactory(am.keeper, s)) + reg.Add(weights.Get("msg_vote", 90), simulation.MsgVoteFactory(am.keeper, s)) + reg.Add(weights.Get("msg_exec", 90), simulation.MsgExecFactory(am.keeper, s)) + reg.Add(weights.Get("msg_leave_group", 5), simulation.MsgLeaveGroupFactory(am.keeper, s)) } diff --git a/x/group/simulation/genesis.go b/x/group/simulation/genesis.go index 2049e8a4ac15..f94fe0da18c1 100644 --- a/x/group/simulation/genesis.go +++ b/x/group/simulation/genesis.go @@ -4,7 +4,6 @@ import ( "math/rand" "time" - "cosmossdk.io/core/address" banktypes "cosmossdk.io/x/bank/types" "cosmossdk.io/x/group" @@ -31,14 +30,11 @@ func checkAccExists(acc string, g []*group.GroupMember, lastIndex int) bool { return false } -func getGroups(r *rand.Rand, accounts []simtypes.Account, addressCodec address.Codec) []*group.GroupInfo { +func getGroups(r *rand.Rand, accounts []simtypes.Account) []*group.GroupInfo { groups := make([]*group.GroupInfo, 3) for i := 0; i < 3; i++ { acc, _ := simtypes.RandomAcc(r, accounts) - accAddr, err := addressCodec.BytesToString(acc.Address) - if err != nil { - return nil - } + accAddr := acc.AddressBech32 groups[i] = &group.GroupInfo{ Id: uint64(i + 1), Admin: accAddr, @@ -50,20 +46,14 @@ func getGroups(r *rand.Rand, accounts []simtypes.Account, addressCodec address.C return groups } -func getGroupMembers(r *rand.Rand, accounts []simtypes.Account, addressCodec address.Codec) []*group.GroupMember { +func getGroupMembers(r *rand.Rand, accounts []simtypes.Account) []*group.GroupMember { groupMembers := make([]*group.GroupMember, 3) for i := 0; i < 3; i++ { acc, _ := simtypes.RandomAcc(r, accounts) - accAddr, err := addressCodec.BytesToString(acc.Address) - if err != nil { - return nil - } + accAddr := acc.AddressBech32 for checkAccExists(accAddr, groupMembers, i) { acc, _ = simtypes.RandomAcc(r, accounts) - accAddr, err = addressCodec.BytesToString(acc.Address) - if err != nil { - return nil - } + accAddr = acc.AddressBech32 } groupMembers[i] = &group.GroupMember{ GroupId: uint64(i + 1), @@ -83,11 +73,7 @@ func getGroupPolicies(r *rand.Rand, simState *module.SimulationState) []*group.G usedAccs := make(map[string]bool) for i := 0; i < 3; i++ { acc, _ := simtypes.RandomAcc(r, simState.Accounts) - accAddr, err := simState.AddressCodec.BytesToString(acc.Address) - if err != nil { - return nil - } - if usedAccs[accAddr] { + if usedAccs[acc.AddressBech32] { if len(usedAccs) != len(simState.Accounts) { // Go again if the account is used and there are more to take from i-- @@ -95,7 +81,7 @@ func getGroupPolicies(r *rand.Rand, simState *module.SimulationState) []*group.G continue } - usedAccs[accAddr] = true + usedAccs[acc.AddressBech32] = true any, err := codectypes.NewAnyWithValue(group.NewThresholdDecisionPolicy("10", time.Second, 0)) if err != nil { @@ -103,8 +89,8 @@ func getGroupPolicies(r *rand.Rand, simState *module.SimulationState) []*group.G } groupPolicies = append(groupPolicies, &group.GroupPolicyInfo{ GroupId: uint64(i + 1), - Admin: accAddr, - Address: accAddr, + Admin: acc.AddressBech32, + Address: acc.AddressBech32, Version: 1, DecisionPolicy: any, Metadata: simtypes.RandStringOfLength(r, 10), @@ -115,14 +101,8 @@ func getGroupPolicies(r *rand.Rand, simState *module.SimulationState) []*group.G func getProposals(r *rand.Rand, simState *module.SimulationState, groupPolicies []*group.GroupPolicyInfo) []*group.Proposal { proposals := make([]*group.Proposal, 3) - addr0, err := simState.AddressCodec.BytesToString(simState.Accounts[0].Address) - if err != nil { - panic(err) - } - addr1, err := simState.AddressCodec.BytesToString(simState.Accounts[1].Address) - if err != nil { - panic(err) - } + addr0 := simState.Accounts[0].AddressBech32 + addr1 := simState.Accounts[1].AddressBech32 proposers := []string{addr0, addr1} for i := 0; i < 3; i++ { idx := r.Intn(len(groupPolicies)) @@ -151,14 +131,9 @@ func getProposals(r *rand.Rand, simState *module.SimulationState, groupPolicies VotingPeriodEnd: timeout, } - toAddr, err := simState.AddressCodec.BytesToString(to.Address) - if err != nil { - panic(err) - } - - err = proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{ + err := proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{ FromAddress: groupPolicyAddress, - ToAddress: toAddr, + ToAddress: to.AddressBech32, Amount: sdk.NewCoins(sdk.NewInt64Coin("test", 10)), }}) if err != nil { @@ -175,10 +150,7 @@ func getVotes(r *rand.Rand, simState *module.SimulationState) []*group.Vote { votes := make([]*group.Vote, 3) for i := 0; i < 3; i++ { - voterAddr, err := simState.AddressCodec.BytesToString(simState.Accounts[i].Address) - if err != nil { - return nil - } + voterAddr := simState.Accounts[i].AddressBech32 votes[i] = &group.Vote{ ProposalId: uint64(i + 1), Voter: voterAddr, @@ -213,11 +185,11 @@ func RandomizedGenState(simState *module.SimulationState) { // groups var groups []*group.GroupInfo - simState.AppParams.GetOrGenerate(GroupInfo, &groups, simState.Rand, func(r *rand.Rand) { groups = getGroups(r, simState.Accounts, simState.AddressCodec) }) + simState.AppParams.GetOrGenerate(GroupInfo, &groups, simState.Rand, func(r *rand.Rand) { groups = getGroups(r, simState.Accounts) }) // group members var members []*group.GroupMember - simState.AppParams.GetOrGenerate(GroupMembers, &members, simState.Rand, func(r *rand.Rand) { members = getGroupMembers(r, simState.Accounts, simState.AddressCodec) }) + simState.AppParams.GetOrGenerate(GroupMembers, &members, simState.Rand, func(r *rand.Rand) { members = getGroupMembers(r, simState.Accounts) }) // group policies var groupPolicies []*group.GroupPolicyInfo diff --git a/x/group/simulation/msg_factory.go b/x/group/simulation/msg_factory.go new file mode 100644 index 000000000000..30b55fc498ce --- /dev/null +++ b/x/group/simulation/msg_factory.go @@ -0,0 +1,487 @@ +package simulation + +import ( + "context" + "slices" + "strconv" + "sync/atomic" + "time" + + "cosmossdk.io/x/group" + "cosmossdk.io/x/group/keeper" + + "github.com/cosmos/cosmos-sdk/simsx" +) + +const unsetGroupID = 100000000000000 + +// SharedState shared state between message invocations +type SharedState struct { + minGroupID atomic.Uint64 +} + +// NewSharedState constructor +func NewSharedState() *SharedState { + r := &SharedState{} + r.setMinGroupID(unsetGroupID) + return r +} + +func (s *SharedState) getMinGroupID() uint64 { + return s.minGroupID.Load() +} + +func (s *SharedState) setMinGroupID(id uint64) { + s.minGroupID.Store(id) +} + +func MsgCreateGroupFactory() simsx.SimMsgFactoryFn[*group.MsgCreateGroup] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgCreateGroup) { + admin := testData.AnyAccount(reporter, simsx.WithSpendableBalance()) + members := genGroupMembersX(testData, reporter) + msg := &group.MsgCreateGroup{Admin: admin.AddressBech32, Members: members, Metadata: testData.Rand().StringN(10)} + return []simsx.SimAccount{admin}, msg + } +} + +func MsgCreateGroupPolicyFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgCreateGroupPolicy] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgCreateGroupPolicy) { + groupInfo := randomGroupX(ctx, k, testData, reporter, s) + groupAdmin := testData.GetAccount(reporter, groupInfo.Admin) + if reporter.IsSkipped() { + return nil, nil + } + groupID := groupInfo.Id + + r := testData.Rand() + msg, err := group.NewMsgCreateGroupPolicy( + groupAdmin.AddressBech32, + groupID, + r.StringN(10), + &group.ThresholdDecisionPolicy{ + Threshold: strconv.Itoa(r.IntInRange(1, 10)), + Windows: &group.DecisionPolicyWindows{ + VotingPeriod: time.Second * time.Duration(30*24*60*60), + }, + }, + ) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + return []simsx.SimAccount{groupAdmin}, msg + } +} + +func MsgCreateGroupWithPolicyFactory() simsx.SimMsgFactoryFn[*group.MsgCreateGroupWithPolicy] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgCreateGroupWithPolicy) { + admin := testData.AnyAccount(reporter, simsx.WithSpendableBalance()) + members := genGroupMembersX(testData, reporter) + r := testData.Rand() + msg := &group.MsgCreateGroupWithPolicy{ + Admin: admin.AddressBech32, + Members: members, + GroupMetadata: r.StringN(10), + GroupPolicyMetadata: r.StringN(10), + GroupPolicyAsAdmin: r.Float32() < 0.5, + } + decisionPolicy := &group.ThresholdDecisionPolicy{ + Threshold: strconv.Itoa(r.IntInRange(1, 10)), + Windows: &group.DecisionPolicyWindows{ + VotingPeriod: time.Second * time.Duration(30*24*60*60), + }, + } + if err := msg.SetDecisionPolicy(decisionPolicy); err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + return []simsx.SimAccount{admin}, msg + } +} + +func MsgWithdrawProposalFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgWithdrawProposal] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgWithdrawProposal) { + groupInfo, groupPolicy := randomGroupPolicyX(ctx, testData, reporter, k, s) + if reporter.IsSkipped() { + return nil, nil + } + policy, err := groupPolicy.GetDecisionPolicy() + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + err = policy.Validate(*groupInfo, group.DefaultConfig()) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + proposalsResult, err := k.ProposalsByGroupPolicy(ctx, + &group.QueryProposalsByGroupPolicyRequest{Address: groupPolicy.Address}, + ) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + + now := simsx.BlockTime(ctx) + proposal := simsx.First(proposalsResult.GetProposals(), func(p *group.Proposal) bool { + return p.Status == group.PROPOSAL_STATUS_SUBMITTED && p.VotingPeriodEnd.After(now) + }) + if proposal == nil { + reporter.Skip("no proposal found") + return nil, nil + } + // select a random proposer + r := testData.Rand() + proposer := testData.GetAccount(reporter, simsx.OneOf(r, (*proposal).Proposers)) + + msg := &group.MsgWithdrawProposal{ + ProposalId: (*proposal).Id, + Address: proposer.AddressBech32, + } + return []simsx.SimAccount{proposer}, msg + } +} + +func MsgVoteFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgVote] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgVote) { + groupInfo, groupPolicy := randomGroupPolicyX(ctx, testData, reporter, k, s) + if reporter.IsSkipped() { + return nil, nil + } + proposalsResult, err := k.ProposalsByGroupPolicy(ctx, + &group.QueryProposalsByGroupPolicyRequest{Address: groupPolicy.Address}, + ) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + + now := simsx.BlockTime(ctx) + proposal := simsx.First(proposalsResult.GetProposals(), func(p *group.Proposal) bool { + return p.Status == group.PROPOSAL_STATUS_SUBMITTED && p.VotingPeriodEnd.After(now) + }) + if proposal == nil { + reporter.Skip("no proposal found") + return nil, nil + } + // select a random member + r := testData.Rand() + res, err := k.GroupMembers(ctx, &group.QueryGroupMembersRequest{GroupId: groupInfo.Id}) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + if len(res.Members) == 0 { + reporter.Skip("group has no members") + return nil, nil + } + voter := testData.GetAccount(reporter, simsx.OneOf(r, res.Members).Member.Address) + vRes, err := k.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ + ProposalId: (*proposal).Id, + }) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + if slices.ContainsFunc(vRes.Votes, func(v *group.Vote) bool { return v.Voter == voter.AddressBech32 }) { + reporter.Skip("voted already on proposal") + return nil, nil + } + + msg := &group.MsgVote{ + ProposalId: (*proposal).Id, + Voter: voter.AddressBech32, + Option: group.VOTE_OPTION_YES, + Metadata: r.StringN(10), + } + return []simsx.SimAccount{voter}, msg + } +} + +func MsgSubmitProposalFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgSubmitProposal] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgSubmitProposal) { + groupInfo, groupPolicy := randomGroupPolicyX(ctx, testData, reporter, k, s) + if reporter.IsSkipped() { + return nil, nil + } + // Return a no-op if we know the proposal cannot be created + policy, err := groupPolicy.GetDecisionPolicy() + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + + if err = policy.Validate(*groupInfo, group.DefaultConfig()); err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + + // Pick a random member from the group + r := testData.Rand() + res, err := k.GroupMembers(ctx, &group.QueryGroupMembersRequest{GroupId: groupInfo.Id}) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + if len(res.Members) == 0 { + reporter.Skip("group has no members") + return nil, nil + } + proposer := testData.GetAccount(reporter, simsx.OneOf(r, res.Members).Member.Address) + + msg := &group.MsgSubmitProposal{ + GroupPolicyAddress: groupPolicy.Address, + Proposers: []string{proposer.AddressBech32}, + Metadata: r.StringN(10), + Title: "Test Proposal", + Summary: "Summary of the proposal", + } + return []simsx.SimAccount{proposer}, msg + } +} + +func MsgExecFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgExec] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgExec) { + groupPolicy, policyAdmin := randomGroupPolicyWithAdmin(ctx, testData, reporter, k, s) + if reporter.IsSkipped() { + return nil, nil + } + proposalsResult, err := k.ProposalsByGroupPolicy(ctx, + &group.QueryProposalsByGroupPolicyRequest{Address: groupPolicy.Address}, + ) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + + proposal := simsx.First(proposalsResult.GetProposals(), func(p *group.Proposal) bool { + return p.Status == group.PROPOSAL_STATUS_ACCEPTED + }) + if proposal == nil { + reporter.Skip("no proposal found") + return nil, nil + } + + msg := &group.MsgExec{ + ProposalId: (*proposal).Id, + Executor: policyAdmin.AddressBech32, + } + return []simsx.SimAccount{policyAdmin}, msg + } +} + +func randomGroupPolicyWithAdmin(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter, k keeper.Keeper, s *SharedState) (*group.GroupPolicyInfo, simsx.SimAccount) { + for i := 0; i < 5; i++ { + _, groupPolicy := randomGroupPolicyX(ctx, testData, reporter, k, s) + if groupPolicy != nil && testData.HasAccount(groupPolicy.Admin) { + return groupPolicy, testData.GetAccount(reporter, groupPolicy.Admin) + } + } + reporter.Skip("no group policy found with a sims account") + return nil, simsx.SimAccount{} +} + +func MsgUpdateGroupMetadataFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgUpdateGroupMetadata] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgUpdateGroupMetadata) { + groupInfo := randomGroupX(ctx, k, testData, reporter, s) + groupAdmin := testData.GetAccount(reporter, groupInfo.Admin) + if reporter.IsSkipped() { + return nil, nil + } + msg := &group.MsgUpdateGroupMetadata{ + GroupId: groupInfo.Id, + Admin: groupAdmin.AddressBech32, + Metadata: testData.Rand().StringN(10), + } + return []simsx.SimAccount{groupAdmin}, msg + } +} + +func MsgUpdateGroupAdminFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgUpdateGroupAdmin] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgUpdateGroupAdmin) { + groupInfo := randomGroupX(ctx, k, testData, reporter, s) + groupAdmin := testData.GetAccount(reporter, groupInfo.Admin) + if reporter.IsSkipped() { + return nil, nil + } + newAdmin := testData.AnyAccount(reporter, simsx.ExcludeAccounts(groupAdmin)) + msg := &group.MsgUpdateGroupAdmin{ + GroupId: groupInfo.Id, + Admin: groupAdmin.AddressBech32, + NewAdmin: newAdmin.AddressBech32, + } + return []simsx.SimAccount{groupAdmin}, msg + } +} + +func MsgUpdateGroupMembersFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgUpdateGroupMembers] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgUpdateGroupMembers) { + groupInfo := randomGroupX(ctx, k, testData, reporter, s) + groupAdmin := testData.GetAccount(reporter, groupInfo.Admin) + if reporter.IsSkipped() { + return nil, nil + } + res, err := k.GroupMembers(ctx, &group.QueryGroupMembersRequest{GroupId: groupInfo.Id}) + if err != nil { + reporter.Skip("group members not found") + return nil, nil + } + oldMemberAddrs := simsx.Collect(res.Members, func(a *group.GroupMember) string { return a.Member.Address }) + members := genGroupMembersX(testData, reporter, simsx.ExcludeAddresses(oldMemberAddrs...)) + if len(res.Members) != 0 { + // set existing random group member weight to zero to remove from the group + obsoleteMember := simsx.OneOf(testData.Rand(), res.Members) + obsoleteMember.Member.Weight = "0" + members = append(members, group.MemberToMemberRequest(obsoleteMember.Member)) + } + msg := &group.MsgUpdateGroupMembers{ + GroupId: groupInfo.Id, + Admin: groupAdmin.AddressBech32, + MemberUpdates: members, + } + return []simsx.SimAccount{groupAdmin}, msg + } +} + +func MsgUpdateGroupPolicyAdminFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgUpdateGroupPolicyAdmin] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgUpdateGroupPolicyAdmin) { + groupPolicy, policyAdmin := randomGroupPolicyWithAdmin(ctx, testData, reporter, k, s) + if reporter.IsSkipped() { + return nil, nil + } + newAdmin := testData.AnyAccount(reporter, simsx.ExcludeAccounts(policyAdmin)) + msg := &group.MsgUpdateGroupPolicyAdmin{ + Admin: policyAdmin.AddressBech32, + GroupPolicyAddress: groupPolicy.Address, + NewAdmin: newAdmin.AddressBech32, + } + return []simsx.SimAccount{policyAdmin}, msg + } +} + +func MsgUpdateGroupPolicyDecisionPolicyFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgUpdateGroupPolicyDecisionPolicy] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgUpdateGroupPolicyDecisionPolicy) { + groupPolicy, policyAdmin := randomGroupPolicyWithAdmin(ctx, testData, reporter, k, s) + if reporter.IsSkipped() { + return nil, nil + } + r := testData.Rand() + msg, err := group.NewMsgUpdateGroupPolicyDecisionPolicy(policyAdmin.AddressBech32, groupPolicy.Address, &group.ThresholdDecisionPolicy{ + Threshold: strconv.Itoa(r.IntInRange(1, 10)), + Windows: &group.DecisionPolicyWindows{ + VotingPeriod: time.Second * time.Duration(r.IntInRange(100, 1000)), + }, + }) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + return []simsx.SimAccount{policyAdmin}, msg + } +} + +func MsgUpdateGroupPolicyMetadataFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgUpdateGroupPolicyMetadata] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgUpdateGroupPolicyMetadata) { + groupPolicy, policyAdmin := randomGroupPolicyWithAdmin(ctx, testData, reporter, k, s) + if reporter.IsSkipped() { + return nil, nil + } + msg := &group.MsgUpdateGroupPolicyMetadata{ + Admin: policyAdmin.AddressBech32, + GroupPolicyAddress: groupPolicy.Address, + Metadata: testData.Rand().StringN(10), + } + return []simsx.SimAccount{policyAdmin}, msg + } +} + +func MsgLeaveGroupFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgLeaveGroup] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgLeaveGroup) { + groupInfo := randomGroupX(ctx, k, testData, reporter, s) + if reporter.IsSkipped() { + return nil, nil + } + res, err := k.GroupMembers(ctx, &group.QueryGroupMembersRequest{GroupId: groupInfo.Id}) + if err != nil { + reporter.Skip("group members not found") + return nil, nil + } + if len(res.Members) == 0 { + reporter.Skip("group has no members") + return nil, nil + } + anyMember := simsx.OneOf(testData.Rand(), res.Members) + leaver := testData.GetAccount(reporter, anyMember.Member.Address) + msg := &group.MsgLeaveGroup{ + GroupId: groupInfo.Id, + Address: leaver.AddressBech32, + } + return []simsx.SimAccount{leaver}, msg + } +} + +func genGroupMembersX(testData *simsx.ChainDataSource, reporter simsx.SimulationReporter, filters ...simsx.SimAccountFilter) []group.MemberRequest { + r := testData.Rand() + membersCount := r.Intn(5) + 1 + members := make([]group.MemberRequest, membersCount) + uniqueAccountsFilter := simsx.UniqueAccounts() + for i := 0; i < membersCount && !reporter.IsSkipped(); i++ { + m := testData.AnyAccount(reporter, append(filters, uniqueAccountsFilter)...) + members[i] = group.MemberRequest{ + Address: m.AddressBech32, + Weight: strconv.Itoa(r.IntInRange(1, 10)), + Metadata: r.StringN(10), + } + } + return members +} + +func randomGroupX(ctx context.Context, k keeper.Keeper, testdata *simsx.ChainDataSource, reporter simsx.SimulationReporter, s *SharedState) *group.GroupInfo { + r := testdata.Rand() + groupID := k.GetGroupSequence(ctx) + if initialGroupID := s.getMinGroupID(); initialGroupID == unsetGroupID { + s.setMinGroupID(groupID) + } else if initialGroupID < groupID { + groupID = r.Uint64InRange(initialGroupID+1, groupID+1) + } + + // when groupID is 0, it proves that SimulateMsgCreateGroup has never been called. that is, no group exists in the chain + if groupID == 0 { + reporter.Skip("no group exists") + return nil + } + + res, err := k.GroupInfo(ctx, &group.QueryGroupInfoRequest{GroupId: groupID}) + if err != nil { + reporter.Skip(err.Error()) + return nil + } + return res.Info +} + +func randomGroupPolicyX( + ctx context.Context, + testdata *simsx.ChainDataSource, + reporter simsx.SimulationReporter, + k keeper.Keeper, + s *SharedState, +) (*group.GroupInfo, *group.GroupPolicyInfo) { + for i := 0; i < 5; i++ { + groupInfo := randomGroupX(ctx, k, testdata, reporter, s) + if reporter.IsSkipped() { + return nil, nil + } + groupID := groupInfo.Id + result, err := k.GroupPoliciesByGroup(ctx, &group.QueryGroupPoliciesByGroupRequest{GroupId: groupID}) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + if len(result.GroupPolicies) != 0 { + return groupInfo, simsx.OneOf(testdata.Rand(), result.GroupPolicies) + } + } + reporter.Skip("no group policies") + return nil, nil +} diff --git a/x/group/simulation/operations.go b/x/group/simulation/operations.go deleted file mode 100644 index bab0939fddaf..000000000000 --- a/x/group/simulation/operations.go +++ /dev/null @@ -1,1508 +0,0 @@ -package simulation - -import ( - "context" - "fmt" - "math/rand" - "strings" - "sync/atomic" - "time" - - gogoprotoany "github.com/cosmos/gogoproto/types/any" - - "cosmossdk.io/core/address" - "cosmossdk.io/x/group" - "cosmossdk.io/x/group/keeper" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - cdctypes "github.com/cosmos/cosmos-sdk/codec/types" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -const unsetGroupID = 100000000000000 - -// group message types -var ( - TypeMsgCreateGroup = sdk.MsgTypeURL(&group.MsgCreateGroup{}) - TypeMsgUpdateGroupMembers = sdk.MsgTypeURL(&group.MsgUpdateGroupMembers{}) - TypeMsgUpdateGroupAdmin = sdk.MsgTypeURL(&group.MsgUpdateGroupAdmin{}) - TypeMsgUpdateGroupMetadata = sdk.MsgTypeURL(&group.MsgUpdateGroupMetadata{}) - TypeMsgCreateGroupWithPolicy = sdk.MsgTypeURL(&group.MsgCreateGroupWithPolicy{}) - TypeMsgCreateGroupPolicy = sdk.MsgTypeURL(&group.MsgCreateGroupPolicy{}) - TypeMsgUpdateGroupPolicyAdmin = sdk.MsgTypeURL(&group.MsgUpdateGroupPolicyAdmin{}) - TypeMsgUpdateGroupPolicyDecisionPolicy = sdk.MsgTypeURL(&group.MsgUpdateGroupPolicyDecisionPolicy{}) - TypeMsgUpdateGroupPolicyMetadata = sdk.MsgTypeURL(&group.MsgUpdateGroupPolicyMetadata{}) - TypeMsgSubmitProposal = sdk.MsgTypeURL(&group.MsgSubmitProposal{}) - TypeMsgWithdrawProposal = sdk.MsgTypeURL(&group.MsgWithdrawProposal{}) - TypeMsgVote = sdk.MsgTypeURL(&group.MsgVote{}) - TypeMsgExec = sdk.MsgTypeURL(&group.MsgExec{}) - TypeMsgLeaveGroup = sdk.MsgTypeURL(&group.MsgLeaveGroup{}) -) - -// Simulation operation weights constants -const ( - OpMsgCreateGroup = "op_weight_msg_create_group" - OpMsgUpdateGroupAdmin = "op_weight_msg_update_group_admin" - OpMsgUpdateGroupMetadata = "op_wieght_msg_update_group_metadata" - OpMsgUpdateGroupMembers = "op_weight_msg_update_group_members" - OpMsgCreateGroupPolicy = "op_weight_msg_create_group_account" - OpMsgCreateGroupWithPolicy = "op_weight_msg_create_group_with_policy" - OpMsgUpdateGroupPolicyAdmin = "op_weight_msg_update_group_account_admin" - OpMsgUpdateGroupPolicyDecisionPolicy = "op_weight_msg_update_group_account_decision_policy" - OpMsgUpdateGroupPolicyMetaData = "op_weight_msg_update_group_account_metadata" - OpMsgSubmitProposal = "op_weight_msg_submit_proposal" - OpMsgWithdrawProposal = "op_weight_msg_withdraw_proposal" - OpMsgVote = "op_weight_msg_vote" - OpMsgExec = "ops_weight_msg_exec" - OpMsgLeaveGroup = "ops_weight_msg_leave_group" -) - -// If update group or group policy txn's executed, `SimulateMsgVote` & `SimulateMsgExec` txn's returns `noOp`. -// That's why we have less weight for update group & group-policy txn's. -const ( - WeightMsgCreateGroup = 100 - WeightMsgCreateGroupPolicy = 50 - WeightMsgSubmitProposal = 90 - WeightMsgVote = 90 - WeightMsgExec = 90 - WeightMsgLeaveGroup = 5 - WeightMsgUpdateGroupMetadata = 5 - WeightMsgUpdateGroupAdmin = 5 - WeightMsgUpdateGroupMembers = 5 - WeightMsgUpdateGroupPolicyAdmin = 5 - WeightMsgUpdateGroupPolicyDecisionPolicy = 5 - WeightMsgUpdateGroupPolicyMetadata = 5 - WeightMsgWithdrawProposal = 20 - WeightMsgCreateGroupWithPolicy = 50 -) - -// SharedState shared state between message invocations -type SharedState struct { - minGroupID atomic.Uint64 -} - -// NewSharedState constructor -func NewSharedState() *SharedState { - r := &SharedState{} - r.setMinGroupID(unsetGroupID) - return r -} - -func (s *SharedState) getMinGroupID() uint64 { - return s.minGroupID.Load() -} - -func (s *SharedState) setMinGroupID(id uint64) { - s.minGroupID.Store(id) -} - -// WeightedOperations returns all the operations from the module with their respective weights -func WeightedOperations( - registry cdctypes.InterfaceRegistry, - appParams simtypes.AppParams, cdc codec.JSONCodec, txGen client.TxConfig, - ak group.AccountKeeper, bk group.BankKeeper, k keeper.Keeper, - appCdc gogoprotoany.AnyUnpacker, -) simulation.WeightedOperations { - var ( - weightMsgCreateGroup int - weightMsgUpdateGroupAdmin int - weightMsgUpdateGroupMetadata int - weightMsgUpdateGroupMembers int - weightMsgCreateGroupPolicy int - weightMsgUpdateGroupPolicyAdmin int - weightMsgUpdateGroupPolicyDecisionPolicy int - weightMsgUpdateGroupPolicyMetadata int - weightMsgSubmitProposal int - weightMsgVote int - weightMsgExec int - weightMsgLeaveGroup int - weightMsgWithdrawProposal int - weightMsgCreateGroupWithPolicy int - ) - - appParams.GetOrGenerate(OpMsgCreateGroup, &weightMsgCreateGroup, nil, func(_ *rand.Rand) { - weightMsgCreateGroup = WeightMsgCreateGroup - }) - appParams.GetOrGenerate(OpMsgCreateGroupPolicy, &weightMsgCreateGroupPolicy, nil, func(_ *rand.Rand) { - weightMsgCreateGroupPolicy = WeightMsgCreateGroupPolicy - }) - appParams.GetOrGenerate(OpMsgLeaveGroup, &weightMsgLeaveGroup, nil, func(_ *rand.Rand) { - weightMsgLeaveGroup = WeightMsgLeaveGroup - }) - appParams.GetOrGenerate(OpMsgCreateGroupWithPolicy, &weightMsgCreateGroupWithPolicy, nil, func(_ *rand.Rand) { - weightMsgCreateGroupWithPolicy = WeightMsgCreateGroupWithPolicy - }) - appParams.GetOrGenerate(OpMsgSubmitProposal, &weightMsgSubmitProposal, nil, func(_ *rand.Rand) { - weightMsgSubmitProposal = WeightMsgSubmitProposal - }) - appParams.GetOrGenerate(OpMsgVote, &weightMsgVote, nil, func(_ *rand.Rand) { - weightMsgVote = WeightMsgVote - }) - appParams.GetOrGenerate(OpMsgExec, &weightMsgExec, nil, func(_ *rand.Rand) { - weightMsgExec = WeightMsgExec - }) - appParams.GetOrGenerate(OpMsgUpdateGroupMetadata, &weightMsgUpdateGroupMetadata, nil, func(_ *rand.Rand) { - weightMsgUpdateGroupMetadata = WeightMsgUpdateGroupMetadata - }) - appParams.GetOrGenerate(OpMsgUpdateGroupAdmin, &weightMsgUpdateGroupAdmin, nil, func(_ *rand.Rand) { - weightMsgUpdateGroupAdmin = WeightMsgUpdateGroupAdmin - }) - appParams.GetOrGenerate(OpMsgUpdateGroupMembers, &weightMsgUpdateGroupMembers, nil, func(_ *rand.Rand) { - weightMsgUpdateGroupMembers = WeightMsgUpdateGroupMembers - }) - appParams.GetOrGenerate(OpMsgUpdateGroupPolicyAdmin, &weightMsgUpdateGroupPolicyAdmin, nil, func(_ *rand.Rand) { - weightMsgUpdateGroupPolicyAdmin = WeightMsgUpdateGroupPolicyAdmin - }) - appParams.GetOrGenerate(OpMsgUpdateGroupPolicyDecisionPolicy, &weightMsgUpdateGroupPolicyDecisionPolicy, nil, func(_ *rand.Rand) { - weightMsgUpdateGroupPolicyDecisionPolicy = WeightMsgUpdateGroupPolicyDecisionPolicy - }) - appParams.GetOrGenerate(OpMsgUpdateGroupPolicyMetaData, &weightMsgUpdateGroupPolicyMetadata, nil, func(_ *rand.Rand) { - weightMsgUpdateGroupPolicyMetadata = WeightMsgUpdateGroupPolicyMetadata - }) - appParams.GetOrGenerate(OpMsgWithdrawProposal, &weightMsgWithdrawProposal, nil, func(_ *rand.Rand) { - weightMsgWithdrawProposal = WeightMsgWithdrawProposal - }) - - pCdc := codec.NewProtoCodec(registry) - - state := NewSharedState() - - // create two proposals for weightedOperations - var createProposalOps simulation.WeightedOperations - for i := 0; i < 2; i++ { - createProposalOps = append(createProposalOps, simulation.NewWeightedOperation( - weightMsgSubmitProposal, - SimulateMsgSubmitProposal(pCdc, txGen, ak, bk, k, state), - )) - } - - wPreCreateProposalOps := simulation.WeightedOperations{ - simulation.NewWeightedOperation( - weightMsgCreateGroup, - SimulateMsgCreateGroup(pCdc, txGen, ak, bk), - ), - simulation.NewWeightedOperation( - weightMsgCreateGroupPolicy, - SimulateMsgCreateGroupPolicy(pCdc, txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgCreateGroupWithPolicy, - SimulateMsgCreateGroupWithPolicy(pCdc, txGen, ak, bk), - ), - } - - wPostCreateProposalOps := simulation.WeightedOperations{ - simulation.NewWeightedOperation( - WeightMsgWithdrawProposal, - SimulateMsgWithdrawProposal(pCdc, txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgVote, - SimulateMsgVote(pCdc, txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgExec, - SimulateMsgExec(pCdc, txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgUpdateGroupMetadata, - SimulateMsgUpdateGroupMetadata(pCdc, txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgUpdateGroupAdmin, - SimulateMsgUpdateGroupAdmin(pCdc, txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgUpdateGroupMembers, - SimulateMsgUpdateGroupMembers(pCdc, txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgUpdateGroupPolicyAdmin, - SimulateMsgUpdateGroupPolicyAdmin(pCdc, txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgUpdateGroupPolicyDecisionPolicy, - SimulateMsgUpdateGroupPolicyDecisionPolicy(pCdc, txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgUpdateGroupPolicyMetadata, - SimulateMsgUpdateGroupPolicyMetadata(pCdc, txGen, ak, bk, k, state), - ), - simulation.NewWeightedOperation( - weightMsgLeaveGroup, - SimulateMsgLeaveGroup(pCdc, txGen, k, ak, bk, state), - ), - } - - return append(wPreCreateProposalOps, append(createProposalOps, wPostCreateProposalOps...)...) -} - -// SimulateMsgCreateGroup generates a MsgCreateGroup with random values -func SimulateMsgCreateGroup( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - acc, _ := simtypes.RandomAcc(r, accounts) - account := ak.GetAccount(ctx, acc.Address) - accAddr, err := ak.AddressCodec().BytesToString(acc.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroup, "error getting account address"), nil, err - } - - spendableCoins := bk.SpendableCoins(ctx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroup, "fee error"), nil, err - } - - members, err := genGroupMembers(r, accounts, ak.AddressCodec()) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroup, "error generating group members"), nil, err - } - msg := &group.MsgCreateGroup{Admin: accAddr, Members: members, Metadata: simtypes.RandStringOfLength(r, 10)} - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroup, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -// SimulateMsgCreateGroupWithPolicy generates a MsgCreateGroupWithPolicy with random values -func SimulateMsgCreateGroupWithPolicy( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - acc, _ := simtypes.RandomAcc(r, accounts) - account := ak.GetAccount(ctx, acc.Address) - accAddr, err := ak.AddressCodec().BytesToString(acc.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroup, "error getting account address"), nil, err - } - - spendableCoins := bk.SpendableCoins(ctx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroup, "fee error"), nil, err - } - - members, err := genGroupMembers(r, accounts, ak.AddressCodec()) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroup, "error generating group members"), nil, err - } - decisionPolicy := &group.ThresholdDecisionPolicy{ - Threshold: fmt.Sprintf("%d", simtypes.RandIntBetween(r, 1, 10)), - Windows: &group.DecisionPolicyWindows{ - VotingPeriod: time.Second * time.Duration(30*24*60*60), - }, - } - - msg := &group.MsgCreateGroupWithPolicy{ - Admin: accAddr, - Members: members, - GroupMetadata: simtypes.RandStringOfLength(r, 10), - GroupPolicyMetadata: simtypes.RandStringOfLength(r, 10), - GroupPolicyAsAdmin: r.Float32() < 0.5, - } - err = msg.SetDecisionPolicy(decisionPolicy) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to set decision policy"), nil, err - } - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroupWithPolicy, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, nil - } -} - -// SimulateMsgCreateGroupPolicy generates a NewMsgCreateGroupPolicy with random values -func SimulateMsgCreateGroupPolicy( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, - k keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - groupInfo, acc, account, err := randomGroup(r, k, ak, sdkCtx, accounts, s) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroupPolicy, ""), nil, err - } - if groupInfo == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroupPolicy, ""), nil, nil - } - groupID := groupInfo.Id - - spendableCoins := bk.SpendableCoins(sdkCtx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroupPolicy, "fee error"), nil, err - } - - accAddr, err := ak.AddressCodec().BytesToString(acc.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroupPolicy, "error generating admin address"), nil, err - } - - msg, err := group.NewMsgCreateGroupPolicy( - accAddr, - groupID, - simtypes.RandStringOfLength(r, 10), - &group.ThresholdDecisionPolicy{ - Threshold: fmt.Sprintf("%d", simtypes.RandIntBetween(r, 1, 10)), - Windows: &group.DecisionPolicyWindows{ - VotingPeriod: time.Second * time.Duration(30*24*60*60), - }, - }, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroupPolicy, err.Error()), nil, err - } - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroupPolicy, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - fmt.Printf("ERR DELIVER %v\n", err) - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -// SimulateMsgSubmitProposal generates a NewMsgSubmitProposal with random values -func SimulateMsgSubmitProposal( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, - k keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - g, groupPolicy, _, _, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, ""), nil, err - } - if g == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, "no group found"), nil, nil - } - if groupPolicy == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, "no group policy found"), nil, nil - } - groupID := g.Id - groupPolicyAddr := groupPolicy.Address - - // Return a no-op if we know the proposal cannot be created - policy, err := groupPolicy.GetDecisionPolicy() - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, ""), nil, nil - } - err = policy.Validate(*g, group.DefaultConfig()) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, ""), nil, nil - } - - // Pick a random member from the group - acc, account, err := randomMember(sdkCtx, r, k, ak, accounts, groupID) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, ""), nil, err - } - if account == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, "no group member found"), nil, nil - } - - spendableCoins := bk.SpendableCoins(sdkCtx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, "fee error"), nil, err - } - - accAddr, err := ak.AddressCodec().BytesToString(acc.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, "error getting account address"), nil, err - } - - msg := &group.MsgSubmitProposal{ - GroupPolicyAddress: groupPolicyAddr, - Proposers: []string{accAddr}, - Metadata: simtypes.RandStringOfLength(r, 10), - Title: "Test Proposal", - Summary: "Summary of the proposal", - } - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -// SimulateMsgUpdateGroupAdmin generates a MsgUpdateGroupAdmin with random values -func SimulateMsgUpdateGroupAdmin( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, - k keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - groupInfo, acc, account, err := randomGroup(r, k, ak, sdkCtx, accounts, s) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupAdmin, ""), nil, err - } - if groupInfo == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupAdmin, ""), nil, nil - } - groupID := groupInfo.Id - - spendableCoins := bk.SpendableCoins(sdkCtx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupAdmin, "fee error"), nil, err - } - - if len(accounts) == 1 { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupAdmin, "can't set a new admin with only one account"), nil, nil - } - newAdmin, _ := simtypes.RandomAcc(r, accounts) - // disallow setting current admin as new admin - for acc.PubKey.Equals(newAdmin.PubKey) { - newAdmin, _ = simtypes.RandomAcc(r, accounts) - } - - accAddr, err := ak.AddressCodec().BytesToString(account.GetAddress()) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupAdmin, "error getting admin address"), nil, err - } - newAdminAddr, err := ak.AddressCodec().BytesToString(newAdmin.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupAdmin, "error getting new admin address"), nil, err - } - msg := &group.MsgUpdateGroupAdmin{ - GroupId: groupID, - Admin: accAddr, - NewAdmin: newAdminAddr, - } - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupAdmin, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -// SimulateMsgUpdateGroupMetadata generates a MsgUpdateGroupMetadata with random values -func SimulateMsgUpdateGroupMetadata( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, - k keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - groupInfo, acc, account, err := randomGroup(r, k, ak, sdkCtx, accounts, s) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMetadata, ""), nil, err - } - if groupInfo == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMetadata, ""), nil, nil - } - groupID := groupInfo.Id - - spendableCoins := bk.SpendableCoins(sdkCtx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMetadata, "fee error"), nil, err - } - - adminAddr, err := ak.AddressCodec().BytesToString(account.GetAddress()) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMetadata, "error getting admin address"), nil, err - } - msg := &group.MsgUpdateGroupMetadata{ - GroupId: groupID, - Admin: adminAddr, - Metadata: simtypes.RandStringOfLength(r, 10), - } - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMetadata, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -// SimulateMsgUpdateGroupMembers generates a MsgUpdateGroupMembers with random values -func SimulateMsgUpdateGroupMembers( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, - k keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - groupInfo, acc, account, err := randomGroup(r, k, ak, sdkCtx, accounts, s) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMembers, ""), nil, err - } - if groupInfo == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMembers, ""), nil, nil - } - groupID := groupInfo.Id - - spendableCoins := bk.SpendableCoins(sdkCtx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMembers, "fee error"), nil, err - } - - members, err := genGroupMembers(r, accounts, ak.AddressCodec()) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroup, "error generating group members"), nil, err - } - ctx := sdk.UnwrapSDKContext(sdkCtx) - res, err := k.GroupMembers(ctx, &group.QueryGroupMembersRequest{GroupId: groupID}) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMembers, "group members"), nil, err - } - - // set existing random group member weight to zero to remove from the group - existigMembers := res.Members - if len(existigMembers) > 0 { - memberToRemove := existigMembers[r.Intn(len(existigMembers))] - var isDuplicateMember bool - for idx, m := range members { - if m.Address == memberToRemove.Member.Address { - members[idx].Weight = "0" - isDuplicateMember = true - break - } - } - - if !isDuplicateMember { - m := memberToRemove.Member - m.Weight = "0" - members = append(members, group.MemberToMemberRequest(m)) - } - } - - adminAddr, err := ak.AddressCodec().BytesToString(acc.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMembers, "error getting admin address"), nil, err - } - msg := &group.MsgUpdateGroupMembers{ - GroupId: groupID, - Admin: adminAddr, - MemberUpdates: members, - } - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMembers, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -// SimulateMsgUpdateGroupPolicyAdmin generates a MsgUpdateGroupPolicyAdmin with random values -func SimulateMsgUpdateGroupPolicyAdmin( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, - k keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - _, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyAdmin, ""), nil, err - } - if groupPolicy == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyAdmin, "no group policy found"), nil, nil - } - groupPolicyAddr := groupPolicy.Address - - spendableCoins := bk.SpendableCoins(sdkCtx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyAdmin, "fee error"), nil, err - } - - if len(accounts) == 1 { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyAdmin, "can't set a new admin with only one account"), nil, nil - } - newAdmin, _ := simtypes.RandomAcc(r, accounts) - // disallow setting current admin as new admin - for acc.PubKey.Equals(newAdmin.PubKey) { - newAdmin, _ = simtypes.RandomAcc(r, accounts) - } - - adminAddr, err := ak.AddressCodec().BytesToString(acc.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyAdmin, "error getting admin address"), nil, err - } - newAdminAddr, err := ak.AddressCodec().BytesToString(newAdmin.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyAdmin, "error getting new admin address"), nil, err - } - msg := &group.MsgUpdateGroupPolicyAdmin{ - Admin: adminAddr, - GroupPolicyAddress: groupPolicyAddr, - NewAdmin: newAdminAddr, - } - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyAdmin, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -// // SimulateMsgUpdateGroupPolicyDecisionPolicy generates a NewMsgUpdateGroupPolicyDecisionPolicy with random values -func SimulateMsgUpdateGroupPolicyDecisionPolicy( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, - k keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - _, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyDecisionPolicy, ""), nil, err - } - if groupPolicy == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyDecisionPolicy, "no group policy found"), nil, nil - } - groupPolicyAddr := groupPolicy.Address - - spendableCoins := bk.SpendableCoins(sdkCtx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyDecisionPolicy, "fee error"), nil, err - } - - groupPolicyBech32, err := sdk.AccAddressFromBech32(groupPolicyAddr) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyDecisionPolicy, fmt.Sprintf("fail to decide bech32 address: %s", err.Error())), nil, nil - } - - accAddr, err := ak.AddressCodec().BytesToString(acc.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyDecisionPolicy, "error getting admin address"), nil, err - } - groupPolicyStrAddr, err := ak.AddressCodec().BytesToString(groupPolicyBech32) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyDecisionPolicy, "error group policy admin address"), nil, err - } - - msg, err := group.NewMsgUpdateGroupPolicyDecisionPolicy(accAddr, groupPolicyStrAddr, &group.ThresholdDecisionPolicy{ - Threshold: fmt.Sprintf("%d", simtypes.RandIntBetween(r, 1, 10)), - Windows: &group.DecisionPolicyWindows{ - VotingPeriod: time.Second * time.Duration(simtypes.RandIntBetween(r, 100, 1000)), - }, - }) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyDecisionPolicy, err.Error()), nil, err - } - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyDecisionPolicy, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -// // SimulateMsgUpdateGroupPolicyMetadata generates a MsgUpdateGroupPolicyMetadata with random values -func SimulateMsgUpdateGroupPolicyMetadata( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, - k keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - _, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyMetadata, ""), nil, err - } - if groupPolicy == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyMetadata, "no group policy found"), nil, nil - } - groupPolicyAddr := groupPolicy.Address - - spendableCoins := bk.SpendableCoins(sdkCtx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyMetadata, "fee error"), nil, err - } - - adminAddr, err := ak.AddressCodec().BytesToString(acc.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyMetadata, "error getting admin address"), nil, err - } - msg := &group.MsgUpdateGroupPolicyMetadata{ - Admin: adminAddr, - GroupPolicyAddress: groupPolicyAddr, - Metadata: simtypes.RandStringOfLength(r, 10), - } - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyMetadata, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -// SimulateMsgWithdrawProposal generates a MsgWithdrawProposal with random values -func SimulateMsgWithdrawProposal( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, - k keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - g, groupPolicy, _, _, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, ""), nil, err - } - if g == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "no group found"), nil, nil - } - if groupPolicy == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "no group policy found"), nil, nil - } - - groupPolicyAddr := groupPolicy.Address - policy, err := groupPolicy.GetDecisionPolicy() - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, err.Error()), nil, nil - } - err = policy.Validate(*g, group.DefaultConfig()) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, err.Error()), nil, nil - } - - proposalsResult, err := k.ProposalsByGroupPolicy(sdkCtx, &group.QueryProposalsByGroupPolicyRequest{Address: groupPolicyAddr}) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "fail to query group info"), nil, err - } - - proposals := proposalsResult.GetProposals() - if len(proposals) == 0 { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "no proposals found"), nil, nil - } - - var proposal *group.Proposal - proposalID := -1 - - for _, p := range proposals { - if p.Status == group.PROPOSAL_STATUS_SUBMITTED { - timeout := p.VotingPeriodEnd - proposal = p - proposalID = int(p.Id) - if timeout.Before(sdkCtx.HeaderInfo().Time) || timeout.Equal(sdkCtx.HeaderInfo().Time) { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "voting period ended: skipping"), nil, nil - } - break - } - } - - // return no-op if no proposal found - if proposalID == -1 { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "no proposals found"), nil, nil - } - - // select a random proposer - proposers := proposal.Proposers - n := randIntInRange(r, len(proposers)) - proposerIdx, err := findAccount(accounts, proposers[n], ak.AddressCodec()) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "could not find account"), nil, err - } - proposer := accounts[proposerIdx] - proposerAcc := ak.GetAccount(sdkCtx, proposer.Address) - - spendableCoins := bk.SpendableCoins(sdkCtx, proposer.Address) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "fee error"), nil, err - } - - proposerAddr, err := ak.AddressCodec().BytesToString(proposer.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "error getting voter address"), nil, err - } - - msg := &group.MsgWithdrawProposal{ - ProposalId: uint64(proposalID), - Address: proposerAddr, - } - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{proposerAcc.GetAccountNumber()}, - []uint64{proposerAcc.GetSequence()}, - proposer.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyMetadata, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - if strings.Contains(err.Error(), "group was modified") || strings.Contains(err.Error(), "group policy was modified") { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "no-op:group/group-policy was modified"), nil, nil - } - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -// SimulateMsgVote generates a MsgVote with random values -func SimulateMsgVote( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, - k keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - g, groupPolicy, _, _, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, ""), nil, err - } - if g == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no group found"), nil, nil - } - if groupPolicy == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no group policy found"), nil, nil - } - groupPolicyAddr := groupPolicy.Address - - // Pick a random member from the group - acc, account, err := randomMember(sdkCtx, r, k, ak, accounts, g.Id) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, ""), nil, err - } - if account == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no group member found"), nil, nil - } - - spendableCoins := bk.SpendableCoins(sdkCtx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "fee error"), nil, err - } - - proposalsResult, err := k.ProposalsByGroupPolicy(sdkCtx, &group.QueryProposalsByGroupPolicyRequest{Address: groupPolicyAddr}) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "fail to query group info"), nil, err - } - proposals := proposalsResult.GetProposals() - if len(proposals) == 0 { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no proposals found"), nil, nil - } - - proposalID := -1 - - for _, p := range proposals { - if p.Status == group.PROPOSAL_STATUS_SUBMITTED { - timeout := p.VotingPeriodEnd - proposalID = int(p.Id) - if timeout.Before(sdkCtx.HeaderInfo().Time) || timeout.Equal(sdkCtx.HeaderInfo().Time) { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "voting period ended: skipping"), nil, nil - } - break - } - } - - // return no-op if no proposal found - if proposalID == -1 { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no proposals found"), nil, nil - } - - voterAddr, err := ak.AddressCodec().BytesToString(acc.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "error getting voter address"), nil, err - } - - // Ensure member hasn't already voted - res, _ := k.VoteByProposalVoter(sdkCtx, &group.QueryVoteByProposalVoterRequest{ - Voter: voterAddr, - ProposalId: uint64(proposalID), - }) - if res != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "member has already voted"), nil, nil - } - - msg := &group.MsgVote{ - ProposalId: uint64(proposalID), - Voter: voterAddr, - Option: group.VOTE_OPTION_YES, - Metadata: simtypes.RandStringOfLength(r, 10), - } - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyMetadata, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - if strings.Contains(err.Error(), "group was modified") || strings.Contains(err.Error(), "group policy was modified") { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "no-op:group/group-policy was modified"), nil, nil - } - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -// // SimulateMsgExec generates a MsgExec with random values -func SimulateMsgExec( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak group.AccountKeeper, - bk group.BankKeeper, - k keeper.Keeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - _, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, ""), nil, err - } - if groupPolicy == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, "no group policy found"), nil, nil - } - groupPolicyAddr := groupPolicy.Address - - spendableCoins := bk.SpendableCoins(sdkCtx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, "fee error"), nil, err - } - - proposalsResult, err := k.ProposalsByGroupPolicy(sdkCtx, &group.QueryProposalsByGroupPolicyRequest{Address: groupPolicyAddr}) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, "fail to query group info"), nil, err - } - proposals := proposalsResult.GetProposals() - if len(proposals) == 0 { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, "no proposals found"), nil, nil - } - - proposalID := -1 - - for _, proposal := range proposals { - if proposal.Status == group.PROPOSAL_STATUS_ACCEPTED { - proposalID = int(proposal.Id) - break - } - } - - // return no-op if no proposal found - if proposalID == -1 { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, "no proposals found"), nil, nil - } - - accAddr, err := ak.AddressCodec().BytesToString(acc.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, "error getting executor address"), nil, err - } - msg := &group.MsgExec{ - ProposalId: uint64(proposalID), - Executor: accAddr, - } - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyMetadata, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - if strings.Contains(err.Error(), "group was modified") || strings.Contains(err.Error(), "group policy was modified") { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "no-op:group/group-policy was modified"), nil, nil - } - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -// SimulateMsgLeaveGroup generates a MsgLeaveGroup with random values -func SimulateMsgLeaveGroup( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - k keeper.Keeper, - ak group.AccountKeeper, - bk group.BankKeeper, - s *SharedState, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - groupInfo, policyInfo, _, _, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgLeaveGroup, ""), nil, err - } - - if policyInfo == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgLeaveGroup, "no policy found"), nil, nil - } - - // Pick a random member from the group - acc, account, err := randomMember(sdkCtx, r, k, ak, accounts, groupInfo.Id) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgLeaveGroup, ""), nil, err - } - if account == nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgLeaveGroup, "no group member found"), nil, nil - } - - spendableCoins := bk.SpendableCoins(sdkCtx, acc.Address) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgLeaveGroup, "fee error"), nil, err - } - - accAddr, err := ak.AddressCodec().BytesToString(acc.Address) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgLeaveGroup, "error getting account address"), nil, err - } - msg := &group.MsgLeaveGroup{ - Address: accAddr, - GroupId: groupInfo.Id, - } - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - acc.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgLeaveGroup, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), err.Error()), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, err - } -} - -func randomGroup(r *rand.Rand, k keeper.Keeper, ak group.AccountKeeper, - ctx sdk.Context, accounts []simtypes.Account, s *SharedState, -) (groupInfo *group.GroupInfo, acc simtypes.Account, account sdk.AccountI, err error) { - groupID := k.GetGroupSequence(ctx) - - if initialGroupID := s.getMinGroupID(); initialGroupID == unsetGroupID { - s.setMinGroupID(groupID) - } else if initialGroupID < groupID { - groupID = uint64(simtypes.RandIntBetween(r, int(initialGroupID+1), int(groupID+1))) - } - - // when groupID is 0, it proves that SimulateMsgCreateGroup has never been called. that is, no group exists in the chain - if groupID == 0 { - return nil, simtypes.Account{}, nil, nil - } - - res, err := k.GroupInfo(ctx, &group.QueryGroupInfoRequest{GroupId: groupID}) - if err != nil { - return nil, simtypes.Account{}, nil, err - } - - groupInfo = res.Info - groupAdmin := groupInfo.Admin - found := -1 - for i := range accounts { - addr, err := ak.AddressCodec().BytesToString(accounts[i].Address) - if err != nil { - return nil, simtypes.Account{}, nil, err - } - if addr == groupAdmin { - found = i - break - } - } - if found < 0 { - return nil, simtypes.Account{}, nil, nil - } - acc = accounts[found] - account = ak.GetAccount(ctx, acc.Address) - return groupInfo, acc, account, nil -} - -func randomGroupPolicy(r *rand.Rand, k keeper.Keeper, ak group.AccountKeeper, - ctx sdk.Context, accounts []simtypes.Account, s *SharedState, -) (groupInfo *group.GroupInfo, groupPolicyInfo *group.GroupPolicyInfo, acc simtypes.Account, account sdk.AccountI, err error) { - groupInfo, _, _, err = randomGroup(r, k, ak, ctx, accounts, s) - if err != nil { - return nil, nil, simtypes.Account{}, nil, err - } - if groupInfo == nil { - return nil, nil, simtypes.Account{}, nil, nil - } - groupID := groupInfo.Id - - result, err := k.GroupPoliciesByGroup(ctx, &group.QueryGroupPoliciesByGroupRequest{GroupId: groupID}) - if err != nil { - return groupInfo, nil, simtypes.Account{}, nil, err - } - - n := randIntInRange(r, len(result.GroupPolicies)) - if n < 0 { - return groupInfo, nil, simtypes.Account{}, nil, nil - } - groupPolicyInfo = result.GroupPolicies[n] - - idx, err := findAccount(accounts, groupPolicyInfo.Admin, ak.AddressCodec()) - if err != nil { - return groupInfo, nil, simtypes.Account{}, nil, nil - } - - if idx < 0 { - return groupInfo, nil, simtypes.Account{}, nil, nil - } - acc = accounts[idx] - account = ak.GetAccount(ctx, acc.Address) - return groupInfo, groupPolicyInfo, acc, account, nil -} - -func randomMember(ctx context.Context, r *rand.Rand, k keeper.Keeper, ak group.AccountKeeper, - accounts []simtypes.Account, groupID uint64, -) (acc simtypes.Account, account sdk.AccountI, err error) { - res, err := k.GroupMembers(ctx, &group.QueryGroupMembersRequest{ - GroupId: groupID, - }) - if err != nil { - return simtypes.Account{}, nil, err - } - n := randIntInRange(r, len(res.Members)) - if n < 0 { - return simtypes.Account{}, nil, err - } - idx, err := findAccount(accounts, res.Members[n].Member.Address, ak.AddressCodec()) - if err != nil { - return simtypes.Account{}, nil, err - } - if idx < 0 { - return simtypes.Account{}, nil, err - } - acc = accounts[idx] - account = ak.GetAccount(sdk.UnwrapSDKContext(ctx), acc.Address) - return acc, account, nil -} - -func randIntInRange(r *rand.Rand, l int) int { - if l == 0 { - return -1 - } - if l == 1 { - return 0 - } - return simtypes.RandIntBetween(r, 0, l-1) -} - -func findAccount(accounts []simtypes.Account, addr string, addressCodec address.Codec) (idx int, err error) { - idx = -1 - for i := range accounts { - accAddr, err := addressCodec.BytesToString(accounts[i].Address) - if err != nil { - return idx, err - } - if accAddr == addr { - idx = i - break - } - } - return idx, err -} - -func genGroupMembers(r *rand.Rand, accounts []simtypes.Account, addressCodec address.Codec) ([]group.MemberRequest, error) { - if len(accounts) == 1 { - addr, err := addressCodec.BytesToString(accounts[0].Address) - if err != nil { - return nil, err - } - return []group.MemberRequest{ - { - Address: addr, - Weight: fmt.Sprintf("%d", simtypes.RandIntBetween(r, 1, 10)), - Metadata: simtypes.RandStringOfLength(r, 10), - }, - }, nil - } - - max := 5 - if len(accounts) < max { - max = len(accounts) - } - - membersLen := simtypes.RandIntBetween(r, 1, max) - members := make([]group.MemberRequest, membersLen) - - for i := 0; i < membersLen; i++ { - addr, err := addressCodec.BytesToString(accounts[i].Address) - if err != nil { - return nil, err - } - members[i] = group.MemberRequest{ - Address: addr, - Weight: fmt.Sprintf("%d", simtypes.RandIntBetween(r, 1, 10)), - Metadata: simtypes.RandStringOfLength(r, 10), - } - } - - return members, nil -} diff --git a/x/group/simulation/operations_test.go b/x/group/simulation/operations_test.go deleted file mode 100644 index 598e6892b1fa..000000000000 --- a/x/group/simulation/operations_test.go +++ /dev/null @@ -1,759 +0,0 @@ -package simulation_test - -import ( - "math/rand" - "testing" - "time" - - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/suite" - - "cosmossdk.io/depinject" - "cosmossdk.io/log" - bankkeeper "cosmossdk.io/x/bank/keeper" - "cosmossdk.io/x/bank/testutil" - banktypes "cosmossdk.io/x/bank/types" - "cosmossdk.io/x/group" - groupkeeper "cosmossdk.io/x/group/keeper" - "cosmossdk.io/x/group/simulation" - grouptestutil "cosmossdk.io/x/group/testutil" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/runtime" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" -) - -type SimTestSuite struct { - suite.Suite - - ctx sdk.Context - app *runtime.App - codec codec.Codec - interfaceRegistry codectypes.InterfaceRegistry - txConfig client.TxConfig - accountKeeper authkeeper.AccountKeeper - bankKeeper bankkeeper.Keeper - groupKeeper groupkeeper.Keeper -} - -func (suite *SimTestSuite) SetupTest() { - app, err := simtestutil.Setup( - depinject.Configs( - grouptestutil.AppConfig, - depinject.Supply(log.NewNopLogger()), - ), - &suite.codec, - &suite.interfaceRegistry, - &suite.txConfig, - &suite.accountKeeper, - &suite.bankKeeper, - &suite.groupKeeper, - ) - suite.Require().NoError(err) - - suite.app = app - suite.ctx = app.BaseApp.NewContext(false) -} - -func (suite *SimTestSuite) TestWeightedOperations() { - cdc := suite.codec - appParams := make(simtypes.AppParams) - - weightedOps := simulation.WeightedOperations(suite.interfaceRegistry, appParams, cdc, suite.txConfig, suite.accountKeeper, - suite.bankKeeper, suite.groupKeeper, cdc, - ) - - s := rand.NewSource(2) - r := rand.New(s) - accs := suite.getTestingAccounts(r, 3) - - expected := []struct { - weight int - opMsgRoute string - opMsgName string - }{ - {simulation.WeightMsgCreateGroup, group.ModuleName, simulation.TypeMsgCreateGroup}, - {simulation.WeightMsgCreateGroupPolicy, group.ModuleName, simulation.TypeMsgCreateGroupPolicy}, - {simulation.WeightMsgCreateGroupWithPolicy, group.ModuleName, simulation.TypeMsgCreateGroupWithPolicy}, - {simulation.WeightMsgSubmitProposal, group.ModuleName, simulation.TypeMsgSubmitProposal}, - {simulation.WeightMsgSubmitProposal, group.ModuleName, simulation.TypeMsgSubmitProposal}, - {simulation.WeightMsgWithdrawProposal, group.ModuleName, simulation.TypeMsgWithdrawProposal}, - {simulation.WeightMsgVote, group.ModuleName, simulation.TypeMsgVote}, - {simulation.WeightMsgExec, group.ModuleName, simulation.TypeMsgExec}, - {simulation.WeightMsgUpdateGroupMetadata, group.ModuleName, simulation.TypeMsgUpdateGroupMetadata}, - {simulation.WeightMsgUpdateGroupAdmin, group.ModuleName, simulation.TypeMsgUpdateGroupAdmin}, - {simulation.WeightMsgUpdateGroupMembers, group.ModuleName, simulation.TypeMsgUpdateGroupMembers}, - {simulation.WeightMsgUpdateGroupPolicyAdmin, group.ModuleName, simulation.TypeMsgUpdateGroupPolicyAdmin}, - {simulation.WeightMsgUpdateGroupPolicyDecisionPolicy, group.ModuleName, simulation.TypeMsgUpdateGroupPolicyDecisionPolicy}, - {simulation.WeightMsgUpdateGroupPolicyMetadata, group.ModuleName, simulation.TypeMsgUpdateGroupPolicyMetadata}, - {simulation.WeightMsgLeaveGroup, group.ModuleName, simulation.TypeMsgLeaveGroup}, - } - - for i, w := range weightedOps { - operationMsg, _, err := w.Op()(r, suite.app.BaseApp, suite.ctx, accs, "") - suite.Require().NoError(err) - - // the following checks are very much dependent from the ordering of the output given - // by WeightedOperations. if the ordering in WeightedOperations changes some tests - // will fail - suite.Require().Equal(expected[i].weight, w.Weight(), "weight should be the same") - suite.Require().Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") - suite.Require().Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") - } -} - -func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { - accounts := simtypes.RandomAccounts(r, n) - - initAmt := sdk.TokensFromConsensusPower(200, sdk.DefaultPowerReduction) - initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) - - // add coins to the accounts - for _, account := range accounts { - acc := suite.accountKeeper.NewAccountWithAddress(suite.ctx, account.Address) - suite.accountKeeper.SetAccount(suite.ctx, acc) - suite.Require().NoError(testutil.FundAccount(suite.ctx, suite.bankKeeper, account.Address, initCoins)) - } - - return accounts -} - -func (suite *SimTestSuite) TestSimulateCreateGroup() { - // setup 1 account - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 1) - - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgCreateGroup(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgCreateGroup - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(accAddr, msg.Admin) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateCreateGroupWithPolicy() { - // setup 1 account - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 1) - - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgCreateGroupWithPolicy(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgCreateGroupWithPolicy - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(accAddr, msg.Admin) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateCreateGroupPolicy() { - // setup 1 account - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 1) - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // setup a group - _, err = suite.groupKeeper.CreateGroup(suite.ctx, - &group.MsgCreateGroup{ - Admin: accAddr, - Members: []group.MemberRequest{ - { - Address: accAddr, - Weight: "1", - }, - }, - }, - ) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgCreateGroupPolicy(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.groupKeeper, simulation.NewSharedState()) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgCreateGroupPolicy - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(accAddr, msg.Admin) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateSubmitProposal() { - // setup 1 account - s := rand.NewSource(2) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 1) - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // setup a group - ctx := suite.ctx - groupRes, err := suite.groupKeeper.CreateGroup(ctx, - &group.MsgCreateGroup{ - Admin: accAddr, - Members: []group.MemberRequest{ - { - Address: accAddr, - Weight: "1", - }, - }, - }, - ) - suite.Require().NoError(err) - - // setup a group account - accountReq := &group.MsgCreateGroupPolicy{ - Admin: accAddr, - GroupId: groupRes.GroupId, - } - err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0)) - suite.Require().NoError(err) - groupPolicyRes, err := suite.groupKeeper.CreateGroupPolicy(ctx, accountReq) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgSubmitProposal(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.groupKeeper, simulation.NewSharedState()) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgSubmitProposal - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(groupPolicyRes.Address, msg.GroupPolicyAddress) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestWithdrawProposal() { - // setup 1 account - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 3) - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // setup a group - ctx := suite.ctx - addr := accAddr - groupRes, err := suite.groupKeeper.CreateGroup(ctx, - &group.MsgCreateGroup{ - Admin: addr, - Members: []group.MemberRequest{ - { - Address: addr, - Weight: "1", - }, - }, - }, - ) - suite.Require().NoError(err) - - // setup a group account - accountReq := &group.MsgCreateGroupPolicy{ - Admin: addr, - GroupId: groupRes.GroupId, - } - err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0)) - suite.Require().NoError(err) - groupPolicyRes, err := suite.groupKeeper.CreateGroupPolicy(ctx, accountReq) - suite.Require().NoError(err) - - // setup a proposal - proposalReq, err := group.NewMsgSubmitProposal(groupPolicyRes.Address, []string{addr}, []sdk.Msg{ - &banktypes.MsgSend{ - FromAddress: groupPolicyRes.Address, - ToAddress: addr, - Amount: sdk.Coins{sdk.NewInt64Coin("token", 100)}, - }, - }, "", 0, "MsgSend", "this is a test proposal") - suite.Require().NoError(err) - _, err = suite.groupKeeper.SubmitProposal(ctx, proposalReq) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgWithdrawProposal(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.groupKeeper, simulation.NewSharedState()) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgWithdrawProposal - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(addr, msg.Address) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateVote() { - // setup 1 account - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 1) - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // setup a group - ctx := suite.ctx - addr := accAddr - groupRes, err := suite.groupKeeper.CreateGroup(ctx, - &group.MsgCreateGroup{ - Admin: addr, - Members: []group.MemberRequest{ - { - Address: addr, - Weight: "1", - }, - }, - }, - ) - suite.Require().NoError(err) - - // setup a group account - accountReq := &group.MsgCreateGroupPolicy{ - Admin: addr, - GroupId: groupRes.GroupId, - Metadata: "", - } - err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0)) - suite.Require().NoError(err) - groupPolicyRes, err := suite.groupKeeper.CreateGroupPolicy(ctx, accountReq) - suite.Require().NoError(err) - - // setup a proposal - proposalReq, err := group.NewMsgSubmitProposal(groupPolicyRes.Address, []string{addr}, []sdk.Msg{ - &banktypes.MsgSend{ - FromAddress: groupPolicyRes.Address, - ToAddress: addr, - Amount: sdk.Coins{sdk.NewInt64Coin("token", 100)}, - }, - }, "", 0, "MsgSend", "this is a test proposal") - suite.Require().NoError(err) - _, err = suite.groupKeeper.SubmitProposal(ctx, proposalReq) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgVote(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.groupKeeper, simulation.NewSharedState()) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgVote - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(addr, msg.Voter) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateExec() { - // setup 1 account - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 1) - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // setup a group - ctx := suite.ctx - addr := accAddr - groupRes, err := suite.groupKeeper.CreateGroup(ctx, - &group.MsgCreateGroup{ - Admin: addr, - Members: []group.MemberRequest{ - { - Address: addr, - Weight: "1", - }, - }, - }, - ) - suite.Require().NoError(err) - - // setup a group account - accountReq := &group.MsgCreateGroupPolicy{ - Admin: addr, - GroupId: groupRes.GroupId, - } - err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0)) - suite.Require().NoError(err) - groupPolicyRes, err := suite.groupKeeper.CreateGroupPolicy(ctx, accountReq) - suite.Require().NoError(err) - - // setup a proposal - proposalReq, err := group.NewMsgSubmitProposal(groupPolicyRes.Address, []string{addr}, []sdk.Msg{ - &banktypes.MsgSend{ - FromAddress: groupPolicyRes.Address, - ToAddress: addr, - Amount: sdk.Coins{sdk.NewInt64Coin("token", 100)}, - }, - }, "", 0, "MsgSend", "this is a test proposal") - suite.Require().NoError(err) - proposalRes, err := suite.groupKeeper.SubmitProposal(ctx, proposalReq) - suite.Require().NoError(err) - - // vote - _, err = suite.groupKeeper.Vote(ctx, &group.MsgVote{ - ProposalId: proposalRes.ProposalId, - Voter: addr, - Option: group.VOTE_OPTION_YES, - Exec: 1, - }) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgExec(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.groupKeeper, simulation.NewSharedState()) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgExec - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(addr, msg.Executor) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateUpdateGroupAdmin() { - // setup 1 account - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 2) - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // setup a group - _, err = suite.groupKeeper.CreateGroup(suite.ctx, - &group.MsgCreateGroup{ - Admin: accAddr, - Members: []group.MemberRequest{ - { - Address: accAddr, - Weight: "1", - }, - }, - }, - ) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgUpdateGroupAdmin(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.groupKeeper, simulation.NewSharedState()) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgUpdateGroupAdmin - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(accAddr, msg.Admin) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateUpdateGroupMetadata() { - // setup 1 account - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 2) - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // setup a group - _, err = suite.groupKeeper.CreateGroup(suite.ctx, - &group.MsgCreateGroup{ - Admin: accAddr, - Members: []group.MemberRequest{ - { - Address: accAddr, - Weight: "1", - }, - }, - }, - ) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgUpdateGroupMetadata(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.groupKeeper, simulation.NewSharedState()) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgUpdateGroupMetadata - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(accAddr, msg.Admin) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateUpdateGroupMembers() { - // setup 1 account - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 2) - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // setup a group - _, err = suite.groupKeeper.CreateGroup(suite.ctx, - &group.MsgCreateGroup{ - Admin: accAddr, - Members: []group.MemberRequest{ - { - Address: accAddr, - Weight: "1", - }, - }, - }, - ) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgUpdateGroupMembers(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.groupKeeper, simulation.NewSharedState()) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgUpdateGroupMembers - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(accAddr, msg.Admin) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateUpdateGroupPolicyAdmin() { - // setup 1 account - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 2) - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // setup a group - ctx := suite.ctx - groupRes, err := suite.groupKeeper.CreateGroup(ctx, - &group.MsgCreateGroup{ - Admin: accAddr, - Members: []group.MemberRequest{ - { - Address: accAddr, - Weight: "1", - }, - }, - }, - ) - suite.Require().NoError(err) - - // setup a group account - accountReq := &group.MsgCreateGroupPolicy{ - Admin: accAddr, - GroupId: groupRes.GroupId, - } - err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0)) - suite.Require().NoError(err) - groupPolicyRes, err := suite.groupKeeper.CreateGroupPolicy(ctx, accountReq) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgUpdateGroupPolicyAdmin(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.groupKeeper, simulation.NewSharedState()) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgUpdateGroupPolicyAdmin - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(groupPolicyRes.Address, msg.GroupPolicyAddress) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateUpdateGroupPolicyDecisionPolicy() { - // setup 1 account - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 2) - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // setup a group - ctx := suite.ctx - groupRes, err := suite.groupKeeper.CreateGroup(ctx, - &group.MsgCreateGroup{ - Admin: accAddr, - Members: []group.MemberRequest{ - { - Address: accAddr, - Weight: "1", - }, - }, - }, - ) - suite.Require().NoError(err) - - // setup a group account - accountReq := &group.MsgCreateGroupPolicy{ - Admin: accAddr, - GroupId: groupRes.GroupId, - } - err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0)) - suite.Require().NoError(err) - groupPolicyRes, err := suite.groupKeeper.CreateGroupPolicy(ctx, accountReq) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgUpdateGroupPolicyDecisionPolicy(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.groupKeeper, simulation.NewSharedState()) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgUpdateGroupPolicyDecisionPolicy - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(groupPolicyRes.Address, msg.GroupPolicyAddress) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateUpdateGroupPolicyMetadata() { - // setup 1 account - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 2) - acc := accounts[0] - accAddr, err := suite.accountKeeper.AddressCodec().BytesToString(acc.Address) - suite.Require().NoError(err) - - // setup a group - ctx := suite.ctx - groupRes, err := suite.groupKeeper.CreateGroup(ctx, - &group.MsgCreateGroup{ - Admin: accAddr, - Members: []group.MemberRequest{ - { - Address: accAddr, - Weight: "1", - }, - }, - }, - ) - suite.Require().NoError(err) - - // setup a group account - accountReq := &group.MsgCreateGroupPolicy{ - Admin: accAddr, - GroupId: groupRes.GroupId, - } - err = accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Hour, 0)) - suite.Require().NoError(err) - groupPolicyRes, err := suite.groupKeeper.CreateGroupPolicy(ctx, accountReq) - suite.Require().NoError(err) - - // execute operation - op := simulation.SimulateMsgUpdateGroupPolicyMetadata(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.accountKeeper, suite.bankKeeper, suite.groupKeeper, simulation.NewSharedState()) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgUpdateGroupPolicyMetadata - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(groupPolicyRes.Address, msg.GroupPolicyAddress) - suite.Require().Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateLeaveGroup() { - s := rand.NewSource(1) - r := rand.New(s) - require := suite.Require() - - // setup 4 account - accounts := suite.getTestingAccounts(r, 4) - admin := accounts[0] - adminAddr, err := suite.accountKeeper.AddressCodec().BytesToString(admin.Address) - suite.Require().NoError(err) - member1, err := suite.accountKeeper.AddressCodec().BytesToString(accounts[1].Address) - suite.Require().NoError(err) - member2, err := suite.accountKeeper.AddressCodec().BytesToString(accounts[2].Address) - suite.Require().NoError(err) - member3, err := suite.accountKeeper.AddressCodec().BytesToString(accounts[3].Address) - suite.Require().NoError(err) - - // setup a group - ctx := suite.ctx - groupRes, err := suite.groupKeeper.CreateGroup(ctx, - &group.MsgCreateGroup{ - Admin: adminAddr, - Members: []group.MemberRequest{ - { - Address: member1, - Weight: "1", - }, - { - Address: member2, - Weight: "2", - }, - { - Address: member3, - Weight: "1", - }, - }, - }, - ) - require.NoError(err) - - // setup a group account - accountReq := &group.MsgCreateGroupPolicy{ - Admin: adminAddr, - GroupId: groupRes.GroupId, - Metadata: "", - } - require.NoError(accountReq.SetDecisionPolicy(group.NewThresholdDecisionPolicy("3", time.Hour, time.Hour))) - _, err = suite.groupKeeper.CreateGroupPolicy(ctx, accountReq) - require.NoError(err) - - // execute operation - op := simulation.SimulateMsgLeaveGroup(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.groupKeeper, suite.accountKeeper, suite.bankKeeper, simulation.NewSharedState()) - operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) - - var msg group.MsgLeaveGroup - err = proto.Unmarshal(operationMsg.Msg, &msg) - suite.Require().NoError(err) - suite.Require().True(operationMsg.OK) - suite.Require().Equal(groupRes.GroupId, msg.GroupId) - suite.Require().Len(futureOperations, 0) -} - -func TestSimTestSuite(t *testing.T) { - suite.Run(t, new(SimTestSuite)) -} diff --git a/x/mint/go.mod b/x/mint/go.mod index 5bbfae401ba9..19c5805435ea 100644 --- a/x/mint/go.mod +++ b/x/mint/go.mod @@ -23,7 +23,7 @@ require ( github.com/stretchr/testify v1.9.0 google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 google.golang.org/grpc v1.66.2 - gotest.tools/v3 v3.5.1 + gotest.tools/v3 v3.5.1 // indirect ) require ( diff --git a/x/mint/module.go b/x/mint/module.go index fa27a6838df2..826dba01f6fc 100644 --- a/x/mint/module.go +++ b/x/mint/module.go @@ -16,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" + simsx "github.com/cosmos/cosmos-sdk/simsx" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) @@ -168,17 +169,12 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { simulation.RandomizedGenState(simState) } -// ProposalMsgs returns msgs used for governance proposals for simulations. -func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { - return simulation.ProposalMsgs() +// ProposalMsgsX returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_update_params", 100), simulation.MsgUpdateParamsFactory()) } // RegisterStoreDecoder registers a decoder for mint module's types. func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.keeper.Schema) } - -// WeightedOperations doesn't return any mint module operation. -func (AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { - return nil -} diff --git a/x/mint/simulation/genesis.go b/x/mint/simulation/genesis.go index 9933efa1971d..c234d27345cf 100644 --- a/x/mint/simulation/genesis.go +++ b/x/mint/simulation/genesis.go @@ -1,8 +1,6 @@ package simulation import ( - "encoding/json" - "fmt" "math/rand" "cosmossdk.io/math" @@ -70,10 +68,5 @@ func RandomizedGenState(simState *module.SimulationState) { mintGenesis := types.NewGenesisState(types.InitialMinter(inflation), params) - bz, err := json.MarshalIndent(&mintGenesis, "", " ") - if err != nil { - panic(err) - } - fmt.Printf("Selected randomly generated minting parameters:\n%s\n", bz) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(mintGenesis) } diff --git a/x/mint/simulation/msg_factory.go b/x/mint/simulation/msg_factory.go new file mode 100644 index 000000000000..45c96359f61e --- /dev/null +++ b/x/mint/simulation/msg_factory.go @@ -0,0 +1,29 @@ +package simulation + +import ( + "context" + + sdkmath "cosmossdk.io/math" + "cosmossdk.io/x/mint/types" + + "github.com/cosmos/cosmos-sdk/simsx" +) + +// MsgUpdateParamsFactory creates a gov proposal for param updates +func MsgUpdateParamsFactory() simsx.SimMsgFactoryFn[*types.MsgUpdateParams] { + return func(_ context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgUpdateParams) { + r := testData.Rand() + params := types.DefaultParams() + params.BlocksPerYear = r.Uint64InRange(1, 1_000_000) + params.GoalBonded = sdkmath.LegacyNewDecWithPrec(int64(r.IntInRange(1, 100)), 2) + params.InflationMin = sdkmath.LegacyNewDecWithPrec(int64(r.IntInRange(1, 50)), 2) + params.InflationMax = sdkmath.LegacyNewDecWithPrec(int64(r.IntInRange(50, 100)), 2) + params.InflationRateChange = sdkmath.LegacyNewDecWithPrec(int64(r.IntInRange(1, 100)), 2) + params.MintDenom = r.StringN(10) + + return nil, &types.MsgUpdateParams{ + Authority: testData.ModuleAccountAddress(reporter, "gov"), + Params: params, + } + } +} diff --git a/x/mint/simulation/proposals_test.go b/x/mint/simulation/proposals_test.go deleted file mode 100644 index abc470a76d81..000000000000 --- a/x/mint/simulation/proposals_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package simulation_test - -import ( - "context" - "math/rand" - "testing" - - "gotest.tools/v3/assert" - - sdkmath "cosmossdk.io/math" - "cosmossdk.io/x/mint/simulation" - "cosmossdk.io/x/mint/types" - - codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" - "github.com/cosmos/cosmos-sdk/types/address" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -func TestProposalMsgs(t *testing.T) { - ac := codectestutil.CodecOptions{}.GetAddressCodec() - // initialize parameters - s := rand.NewSource(1) - r := rand.New(s) - - accounts := simtypes.RandomAccounts(r, 3) - - // execute ProposalMsgs function - weightedProposalMsgs := simulation.ProposalMsgs() - assert.Assert(t, len(weightedProposalMsgs) == 1) - - w0 := weightedProposalMsgs[0] - - // tests w0 interface: - assert.Equal(t, simulation.OpWeightMsgUpdateParams, w0.AppParamsKey()) - assert.Equal(t, simulation.DefaultWeightMsgUpdateParams, w0.DefaultWeight()) - - msg, err := w0.MsgSimulatorFn()(context.Background(), r, accounts, ac) - assert.NilError(t, err) - msgUpdateParams, ok := msg.(*types.MsgUpdateParams) - assert.Assert(t, ok) - - authority, err := ac.BytesToString(address.Module("gov")) - assert.NilError(t, err) - - assert.Equal(t, authority, msgUpdateParams.Authority) - assert.Equal(t, uint64(122877), msgUpdateParams.Params.BlocksPerYear) - assert.DeepEqual(t, sdkmath.LegacyNewDecWithPrec(95, 2), msgUpdateParams.Params.GoalBonded) - assert.DeepEqual(t, sdkmath.LegacyNewDecWithPrec(94, 2), msgUpdateParams.Params.InflationMax) - assert.DeepEqual(t, sdkmath.LegacyNewDecWithPrec(23, 2), msgUpdateParams.Params.InflationMin) - assert.DeepEqual(t, sdkmath.LegacyNewDecWithPrec(89, 2), msgUpdateParams.Params.InflationRateChange) - assert.Equal(t, "XhhuTSkuxK", msgUpdateParams.Params.MintDenom) -} diff --git a/x/nft/module/module.go b/x/nft/module/module.go index 70672dbab19a..16b82baf2a23 100644 --- a/x/nft/module/module.go +++ b/x/nft/module/module.go @@ -17,6 +17,7 @@ import ( sdkclient "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/simsx" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) @@ -130,11 +131,6 @@ func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[keeper.StoreKey] = simulation.NewDecodeStore(am.cdc) } -// WeightedOperations returns the all the nft module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return simulation.WeightedOperations( - am.registry, - simState.AppParams, simState.Cdc, simState.TxConfig, - am.accountKeeper, am.bankKeeper, am.keeper, - ) +func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_send", 100), simulation.MsgSendFactory(am.keeper)) } diff --git a/x/nft/simulation/msg_factory.go b/x/nft/simulation/msg_factory.go new file mode 100644 index 000000000000..14db2666dee9 --- /dev/null +++ b/x/nft/simulation/msg_factory.go @@ -0,0 +1,66 @@ +package simulation + +import ( + "context" + + "cosmossdk.io/x/nft" + "cosmossdk.io/x/nft/keeper" + + "github.com/cosmos/cosmos-sdk/simsx" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func MsgSendFactory(k keeper.Keeper) simsx.SimMsgFactoryFn[*nft.MsgSend] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *nft.MsgSend) { + from := testData.AnyAccount(reporter, simsx.WithSpendableBalance()) + to := testData.AnyAccount(reporter, simsx.ExcludeAccounts(from)) + + n, err := randNFT(ctx, testData.Rand(), k, from.Address) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + msg := &nft.MsgSend{ + ClassId: n.ClassId, + Id: n.Id, + Sender: from.AddressBech32, + Receiver: to.AddressBech32, + } + + return []simsx.SimAccount{from}, msg + } +} + +// randNFT picks a random NFT from a class belonging to the specified owner(minter). +func randNFT(ctx context.Context, r *simsx.XRand, k keeper.Keeper, minter sdk.AccAddress) (nft.NFT, error) { + c, err := randClass(ctx, r, k) + if err != nil { + return nft.NFT{}, err + } + + if ns := k.GetNFTsOfClassByOwner(ctx, c.Id, minter); len(ns) > 0 { + return ns[r.Intn(len(ns))], nil + } + + n := nft.NFT{ + ClassId: c.Id, + Id: r.StringN(10), + Uri: r.StringN(10), + } + return n, k.Mint(ctx, n, minter) +} + +// randClass picks a random Class. +func randClass(ctx context.Context, r *simsx.XRand, k keeper.Keeper) (nft.Class, error) { + if classes := k.GetClasses(ctx); len(classes) != 0 { + return *classes[r.Intn(len(classes))], nil + } + c := nft.Class{ + Id: r.StringN(10), + Name: r.StringN(10), + Symbol: r.StringN(10), + Description: r.StringN(10), + Uri: r.StringN(10), + } + return c, k.SaveClass(ctx, c) +} diff --git a/x/nft/simulation/operations.go b/x/nft/simulation/operations.go deleted file mode 100644 index f13a47c49e84..000000000000 --- a/x/nft/simulation/operations.go +++ /dev/null @@ -1,170 +0,0 @@ -package simulation - -import ( - "math/rand" - - "cosmossdk.io/x/nft" - "cosmossdk.io/x/nft/keeper" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - cdctypes "github.com/cosmos/cosmos-sdk/codec/types" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -const ( - // OpWeightMsgSend Simulation operation weights constants - OpWeightMsgSend = "op_weight_msg_send" - - // WeightSend nft operations weights - WeightSend = 100 -) - -var TypeMsgSend = sdk.MsgTypeURL(&nft.MsgSend{}) - -// WeightedOperations returns all the operations from the module with their respective weights -func WeightedOperations( - registry cdctypes.InterfaceRegistry, - appParams simtypes.AppParams, - _ codec.JSONCodec, - txCfg client.TxConfig, - ak nft.AccountKeeper, - bk nft.BankKeeper, - k keeper.Keeper, -) simulation.WeightedOperations { - var weightMsgSend int - - appParams.GetOrGenerate(OpWeightMsgSend, &weightMsgSend, nil, - func(_ *rand.Rand) { - weightMsgSend = WeightSend - }, - ) - - return simulation.WeightedOperations{ - simulation.NewWeightedOperation( - weightMsgSend, - SimulateMsgSend(codec.NewProtoCodec(registry), txCfg, ak, bk, k), - ), - } -} - -// SimulateMsgSend generates a MsgSend with random values. -func SimulateMsgSend( - _ *codec.ProtoCodec, - txCfg client.TxConfig, - ak nft.AccountKeeper, - bk nft.BankKeeper, - k keeper.Keeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - sender, _ := simtypes.RandomAcc(r, accs) - receiver, _ := simtypes.RandomAcc(r, accs) - - if sender.Address.Equals(receiver.Address) { - return simtypes.NoOpMsg(nft.ModuleName, TypeMsgSend, "sender and receiver are same"), nil, nil - } - - senderAcc := ak.GetAccount(ctx, sender.Address) - spendableCoins := bk.SpendableCoins(ctx, sender.Address) - fees, err := simtypes.RandomFees(r, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(nft.ModuleName, TypeMsgSend, err.Error()), nil, err - } - - spendLimit := spendableCoins.Sub(fees...) - if spendLimit == nil { - return simtypes.NoOpMsg(nft.ModuleName, TypeMsgSend, "spend limit is nil"), nil, nil - } - - n, err := randNFT(ctx, r, k, senderAcc.GetAddress()) - if err != nil { - return simtypes.NoOpMsg(nft.ModuleName, TypeMsgSend, err.Error()), nil, err - } - - senderStr, err := ak.AddressCodec().BytesToString(senderAcc.GetAddress().Bytes()) - if err != nil { - return simtypes.NoOpMsg(nft.ModuleName, TypeMsgSend, err.Error()), nil, err - } - - receiverStr, err := ak.AddressCodec().BytesToString(receiver.Address.Bytes()) - if err != nil { - return simtypes.NoOpMsg(nft.ModuleName, TypeMsgSend, err.Error()), nil, err - } - - msg := &nft.MsgSend{ - ClassId: n.ClassId, - Id: n.Id, - Sender: senderStr, - Receiver: receiverStr, - } - - tx, err := simtestutil.GenSignedMockTx( - r, - txCfg, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{senderAcc.GetAccountNumber()}, - []uint64{senderAcc.GetSequence()}, - sender.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(nft.ModuleName, TypeMsgSend, "unable to generate mock tx"), nil, err - } - - if _, _, err = app.SimDeliver(txCfg.TxEncoder(), tx); err != nil { - return simtypes.NoOpMsg(nft.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, nil - } -} - -// randNFT picks a random NFT from a class belonging to the specified owner(minter). -func randNFT(ctx sdk.Context, r *rand.Rand, k keeper.Keeper, minter sdk.AccAddress) (nft.NFT, error) { - c, err := randClass(ctx, r, k) - if err != nil { - return nft.NFT{}, err - } - ns := k.GetNFTsOfClassByOwner(ctx, c.Id, minter) - if len(ns) > 0 { - return ns[r.Intn(len(ns))], nil - } - - n := nft.NFT{ - ClassId: c.Id, - Id: simtypes.RandStringOfLength(r, 10), - Uri: simtypes.RandStringOfLength(r, 10), - } - err = k.Mint(ctx, n, minter) - if err != nil { - return nft.NFT{}, err - } - return n, nil -} - -// randClass picks a random Class. -func randClass(ctx sdk.Context, r *rand.Rand, k keeper.Keeper) (nft.Class, error) { - classes := k.GetClasses(ctx) - if len(classes) == 0 { - c := nft.Class{ - Id: simtypes.RandStringOfLength(r, 10), - Name: simtypes.RandStringOfLength(r, 10), - Symbol: simtypes.RandStringOfLength(r, 10), - Description: simtypes.RandStringOfLength(r, 10), - Uri: simtypes.RandStringOfLength(r, 10), - } - err := k.SaveClass(ctx, c) - if err != nil { - return nft.Class{}, err - } - return c, nil - } - return *classes[r.Intn(len(classes))], nil -} diff --git a/x/params/go.mod b/x/params/go.mod index fcab30fbb022..f3ff659f588b 100644 --- a/x/params/go.mod +++ b/x/params/go.mod @@ -30,8 +30,6 @@ require ( buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.34.2-20240130113600-88ef6483f90f.2 // indirect cosmossdk.io/collections v0.4.0 // indirect cosmossdk.io/schema v0.2.0 // indirect - cosmossdk.io/x/bank v0.0.0-20240226161501-23359a0b6d91 // indirect - cosmossdk.io/x/staking v0.0.0-00010101000000-000000000000 // indirect cosmossdk.io/x/tx v0.13.3 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect @@ -52,7 +50,6 @@ require ( github.com/cometbft/cometbft v1.0.0-rc1.0.20240908111210-ab0be101882f // indirect github.com/cometbft/cometbft-db v0.15.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect - github.com/cosmos/cosmos-db v1.0.3-0.20240911104526-ddc3f09bfc22 // indirect github.com/cosmos/crypto v0.1.2 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect @@ -82,8 +79,6 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/flatbuffers v2.0.8+incompatible // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/orderedcode v0.0.1 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect @@ -94,7 +89,6 @@ require ( github.com/hashicorp/go-metrics v0.5.3 // indirect github.com/hashicorp/go-plugin v1.6.1 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect @@ -105,12 +99,10 @@ require ( github.com/klauspost/compress v1.17.9 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.9.3 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/minio/highwayhash v1.0.3 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect @@ -127,7 +119,6 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/rs/cors v1.11.0 // indirect github.com/rs/zerolog v1.33.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/x/params/go.sum b/x/params/go.sum index a658644fd79f..68e15ac01130 100644 --- a/x/params/go.sum +++ b/x/params/go.sum @@ -24,8 +24,6 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMb github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v4.8.3+incompatible h1:fNGaYSuObuQb5nzeTQqowRAd9bpDIRRV4/gUtIBjh8Q= @@ -34,12 +32,8 @@ github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/adlio/schema v1.3.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I= -github.com/adlio/schema v1.3.6/go.mod h1:qkxwLgPBd1FgLRHYVCmQT/rrBr3JH38J9LjmVzWNudg= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -60,8 +54,6 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOF github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -99,8 +91,6 @@ github.com/cometbft/cometbft-db v0.15.0 h1:VLtsRt8udD4jHCyjvrsTBpgz83qne5hnL245A github.com/cometbft/cometbft-db v0.15.0/go.mod h1:EBrFs1GDRiTqrWXYi4v90Awf/gcdD5ExzdPbg4X8+mk= github.com/cometbft/cometbft/api v1.0.0-rc.1 h1:GtdXwDGlqwHYs16A4egjwylfYOMYyEacLBrs3Zvpt7g= github.com/cometbft/cometbft/api v1.0.0-rc.1/go.mod h1:NDFKiBBD8HJC6QQLAoUI99YhsiRZtg2+FJWfk6A6m6o= -github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= -github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= @@ -141,10 +131,6 @@ github.com/dgraph-io/ristretto v0.1.2-0.20240116140435-c67e07994f91 h1:Pux6+xANi github.com/dgraph-io/ristretto v0.1.2-0.20240116140435-c67e07994f91/go.mod h1:swkazRqnUf1N62d0Nutz7KIj2UKqsm/H8tD0nBJAXqM= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY= @@ -320,8 +306,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/linxGnu/grocksdb v1.9.3 h1:s1cbPcOd0cU2SKXRG1nEqCOWYAELQjdqg3RVI2MH9ik= @@ -374,15 +358,7 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= -github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= -github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= -github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= @@ -439,8 +415,6 @@ github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -592,7 +566,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/x/params/module.go b/x/params/module.go index 006afd56b33c..cc531691e9cc 100644 --- a/x/params/module.go +++ b/x/params/module.go @@ -79,10 +79,5 @@ func (am AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error { // RegisterStoreDecoder doesn't register any type. func (AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) {} -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { - return nil -} - // ConsensusVersion implements HasConsensusVersion func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } diff --git a/x/params/simulation/operations.go b/x/params/simulation/operations.go deleted file mode 100644 index c8e6b7de3f63..000000000000 --- a/x/params/simulation/operations.go +++ /dev/null @@ -1,54 +0,0 @@ -package simulation - -import ( - "fmt" - "math/rand" - - "cosmossdk.io/x/params/types/proposal" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/simulation" -) - -func min(a, b int) int { - if a <= b { - return a - } - return b -} - -// SimulateParamChangeProposalContent returns random parameter change content. -// It will generate a ParameterChangeProposal object with anywhere between 1 and -// the total amount of defined parameters changes, all of which have random valid values. -func SimulateParamChangeProposalContent(paramChangePool []simulation.LegacyParamChange) simulation.ContentSimulatorFn { //nolint:staticcheck // used for legacy testing - numProposals := 0 - // Bound the maximum number of simultaneous parameter changes - maxSimultaneousParamChanges := min(len(paramChangePool), 1000) - if maxSimultaneousParamChanges == 0 { - panic("param changes array is empty") - } - - return func(r *rand.Rand, _ sdk.Context, _ []simulation.Account) simulation.Content { //nolint:staticcheck // used for legacy testing - numChanges := simulation.RandIntBetween(r, 1, maxSimultaneousParamChanges) - paramChanges := make([]proposal.ParamChange, numChanges) - - // perm here takes at most len(paramChangePool) calls to random - paramChoices := r.Perm(len(paramChangePool)) - - for i := 0; i < numChanges; i++ { - spc := paramChangePool[paramChoices[i]] - // add a new distinct parameter to the set of changes - paramChanges[i] = proposal.NewParamChange(spc.Subspace(), spc.Key(), spc.SimValue()(r)) - } - - title := fmt.Sprintf("title from SimulateParamChangeProposalContent-%d", numProposals) - desc := fmt.Sprintf("desc from SimulateParamChangeProposalContent-%d. Random short desc: %s", - numProposals, simulation.RandStringOfLength(r, 20)) - numProposals++ - return proposal.NewParameterChangeProposal( - title, // title - desc, // description - paramChanges, // set of changes - ) - } -} diff --git a/x/params/simulation/operations_test.go b/x/params/simulation/operations_test.go deleted file mode 100644 index 65623381cf4a..000000000000 --- a/x/params/simulation/operations_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package simulation_test - -import ( - "fmt" - "math/rand" - "testing" - - "github.com/stretchr/testify/require" - - "cosmossdk.io/x/params/simulation" - "cosmossdk.io/x/params/types/proposal" - - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -type MockParamChange struct { - n int -} - -func (pc MockParamChange) Subspace() string { - return fmt.Sprintf("test-Subspace%d", pc.n) -} - -func (pc MockParamChange) Key() string { - return fmt.Sprintf("test-Key%d", pc.n) -} - -func (pc MockParamChange) ComposedKey() string { - return fmt.Sprintf("test-ComposedKey%d", pc.n) -} - -func (pc MockParamChange) SimValue() simtypes.SimValFn { - return func(r *rand.Rand) string { - return fmt.Sprintf("test-value %d%d ", pc.n, int64(simtypes.RandIntBetween(r, 10, 1000))) - } -} - -// make sure that the MockParamChange satisfied the ParamChange interface -var _ simtypes.LegacyParamChange = MockParamChange{} - -func TestSimulateParamChangeProposalContent(t *testing.T) { - s := rand.NewSource(1) - r := rand.New(s) - - ctx := sdk.NewContext(nil, true, nil) - accounts := simtypes.RandomAccounts(r, 3) - paramChangePool := []simtypes.LegacyParamChange{MockParamChange{1}, MockParamChange{2}, MockParamChange{3}} - - // execute operation - op := simulation.SimulateParamChangeProposalContent(paramChangePool) - content := op(r, ctx, accounts) - - require.Equal(t, "desc from SimulateParamChangeProposalContent-0. Random short desc: IivHSlcxgdXhhuTSkuxK", content.GetDescription()) - require.Equal(t, "title from SimulateParamChangeProposalContent-0", content.GetTitle()) - require.Equal(t, "params", content.ProposalRoute()) - require.Equal(t, "ParameterChange", content.ProposalType()) - - pcp, ok := content.(*proposal.ParameterChangeProposal) - require.True(t, ok) - - require.Equal(t, "test-Key2", pcp.Changes[0].GetKey()) - require.Equal(t, "test-value 2791 ", pcp.Changes[0].GetValue()) - require.Equal(t, "test-Subspace2", pcp.Changes[0].GetSubspace()) -} diff --git a/x/params/simulation/proposals.go b/x/params/simulation/proposals.go deleted file mode 100644 index 8dc636fa05c2..000000000000 --- a/x/params/simulation/proposals.go +++ /dev/null @@ -1,25 +0,0 @@ -package simulation - -import ( - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -const ( - // OpWeightSubmitParamChangeProposal app params key for param change proposal - OpWeightSubmitParamChangeProposal = "op_weight_submit_param_change_proposal" - DefaultWeightParamChangeProposal = 5 -) - -// ProposalContents defines the module weighted proposals' contents -// -//nolint:staticcheck // used for legacy testing -func ProposalContents(paramChanges []simtypes.LegacyParamChange) []simtypes.WeightedProposalContent { - return []simtypes.WeightedProposalContent{ - simulation.NewWeightedProposalContent( - OpWeightSubmitParamChangeProposal, - DefaultWeightParamChangeProposal, - SimulateParamChangeProposalContent(paramChanges), - ), - } -} diff --git a/x/params/simulation/proposals_test.go b/x/params/simulation/proposals_test.go deleted file mode 100644 index 63417433492d..000000000000 --- a/x/params/simulation/proposals_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package simulation_test - -import ( - "math/rand" - "testing" - - "github.com/stretchr/testify/require" - - "cosmossdk.io/x/params/simulation" - "cosmossdk.io/x/params/types/proposal" - - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -func TestProposalContents(t *testing.T) { - // initialize parameters - s := rand.NewSource(1) - r := rand.New(s) - - ctx := sdk.NewContext(nil, true, nil) - accounts := simtypes.RandomAccounts(r, 3) - - paramChangePool := []simtypes.LegacyParamChange{MockParamChange{1}, MockParamChange{2}, MockParamChange{3}} - - // execute ProposalContents function - weightedProposalContent := simulation.ProposalContents(paramChangePool) - require.Len(t, weightedProposalContent, 1) - - w0 := weightedProposalContent[0] - - // tests w0 interface: - require.Equal(t, simulation.OpWeightSubmitParamChangeProposal, w0.AppParamsKey()) - require.Equal(t, simulation.DefaultWeightParamChangeProposal, w0.DefaultWeight()) - - content := w0.ContentSimulatorFn()(r, ctx, accounts) - - require.Equal(t, "desc from SimulateParamChangeProposalContent-0. Random short desc: IivHSlcxgdXhhuTSkuxK", content.GetDescription()) - require.Equal(t, "title from SimulateParamChangeProposalContent-0", content.GetTitle()) - require.Equal(t, "params", content.ProposalRoute()) - require.Equal(t, "ParameterChange", content.ProposalType()) - - pcp, ok := content.(*proposal.ParameterChangeProposal) - require.True(t, ok) - - require.Len(t, pcp.Changes, 1) - require.Equal(t, "test-Key2", pcp.Changes[0].GetKey()) - require.Equal(t, "test-value 2791 ", pcp.Changes[0].GetValue()) - require.Equal(t, "test-Subspace2", pcp.Changes[0].GetSubspace()) -} diff --git a/x/protocolpool/depinject.go b/x/protocolpool/depinject.go index ff07c3319639..a2dc1c950c63 100644 --- a/x/protocolpool/depinject.go +++ b/x/protocolpool/depinject.go @@ -10,6 +10,7 @@ import ( "cosmossdk.io/x/protocolpool/types" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simsx" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -79,15 +80,11 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { } -// ProposalMsgs returns all the protocolpool msgs used to simulate governance proposals. -func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { - return simulation.ProposalMsgs() +// ProposalMsgsX returns all the protocolpool msgs used to simulate governance proposals. +func (am AppModule) ProposalMsgsX(weight simsx.WeightSource, reg simsx.Registry) { + reg.Add(weight.Get("msg_community_pool_spend", 50), simulation.MsgCommunityPoolSpendFactory()) } -// WeightedOperations returns the all the protocolpool module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return simulation.WeightedOperations( - simState.AppParams, simState.Cdc, simState.TxConfig, - am.accountKeeper, am.bankKeeper, am.keeper, - ) +func (am AppModule) WeightedOperationsX(weight simsx.WeightSource, reg simsx.Registry) { + reg.Add(weight.Get("msg_fund_community_pool", 50), simulation.MsgFundCommunityPoolFactory()) } diff --git a/x/protocolpool/go.mod b/x/protocolpool/go.mod index 75d14c4056d1..873110dce2e4 100644 --- a/x/protocolpool/go.mod +++ b/x/protocolpool/go.mod @@ -21,7 +21,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 google.golang.org/grpc v1.66.2 google.golang.org/protobuf v1.34.2 - gotest.tools/v3 v3.5.1 ) require ( @@ -163,6 +162,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.1 // indirect pgregory.net/rapid v1.1.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/x/protocolpool/simulation/msg_factory.go b/x/protocolpool/simulation/msg_factory.go new file mode 100644 index 000000000000..ace70ea2bb81 --- /dev/null +++ b/x/protocolpool/simulation/msg_factory.go @@ -0,0 +1,37 @@ +package simulation + +import ( + "context" + + "cosmossdk.io/x/protocolpool/types" + + "github.com/cosmos/cosmos-sdk/simsx" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func MsgFundCommunityPoolFactory() simsx.SimMsgFactoryFn[*types.MsgFundCommunityPool] { + return func(_ context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgFundCommunityPool) { + funder := testData.AnyAccount(reporter, simsx.WithSpendableBalance()) + fundAmount := funder.LiquidBalance().RandSubsetCoins(reporter) + msg := types.NewMsgFundCommunityPool(fundAmount, funder.AddressBech32) + return []simsx.SimAccount{funder}, msg + } +} + +// MsgCommunityPoolSpendFactory creates a gov proposal to send tokens from the community pool to a random account +func MsgCommunityPoolSpendFactory() simsx.SimMsgFactoryFn[*types.MsgCommunityPoolSpend] { + return func(_ context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgCommunityPoolSpend) { + return nil, &types.MsgCommunityPoolSpend{ + Authority: testData.ModuleAccountAddress(reporter, "gov"), + Recipient: testData.AnyAccount(reporter).AddressBech32, + Amount: must(sdk.ParseCoinsNormalized("100stake,2testtoken")), + } + } +} + +func must[T any](r T, err error) T { + if err != nil { + panic(err) + } + return r +} diff --git a/x/protocolpool/simulation/operations.go b/x/protocolpool/simulation/operations.go deleted file mode 100644 index 2118b4488a50..000000000000 --- a/x/protocolpool/simulation/operations.go +++ /dev/null @@ -1,94 +0,0 @@ -package simulation - -import ( - "math/rand" - - "cosmossdk.io/x/protocolpool/keeper" - "cosmossdk.io/x/protocolpool/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// Simulation operation weights constants -const ( - OpWeightMsgFundCommunityPool = "op_weight_msg_fund_community_pool" - - DefaultWeightMsgFundCommunityPool int = 50 -) - -// WeightedOperations returns all the operations from the module with their respective weights -func WeightedOperations( - appParams simtypes.AppParams, - _ codec.JSONCodec, - txConfig client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k keeper.Keeper, -) simulation.WeightedOperations { - var weightMsgFundCommunityPool int - appParams.GetOrGenerate(OpWeightMsgFundCommunityPool, &weightMsgFundCommunityPool, nil, func(_ *rand.Rand) { - weightMsgFundCommunityPool = DefaultWeightMsgFundCommunityPool - }) - - return simulation.WeightedOperations{ - simulation.NewWeightedOperation( - weightMsgFundCommunityPool, - SimulateMsgFundCommunityPool(txConfig, ak, bk, k), - ), - } -} - -// SimulateMsgFundCommunityPool simulates MsgFundCommunityPool execution where -// a random account sends a random amount of its funds to the community pool. -func SimulateMsgFundCommunityPool(txConfig client.TxConfig, ak types.AccountKeeper, bk types.BankKeeper, _ keeper.Keeper) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - funder, _ := simtypes.RandomAcc(r, accs) - - account := ak.GetAccount(ctx, funder.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - fundAmount := simtypes.RandSubsetCoins(r, spendable) - if fundAmount.Empty() { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgFundCommunityPool{}), "fund amount is empty"), nil, nil - } - - var ( - fees sdk.Coins - err error - ) - - coins, hasNeg := spendable.SafeSub(fundAmount...) - if !hasNeg { - fees, err = simtypes.RandomFees(r, coins) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgFundCommunityPool{}), "unable to generate fees"), nil, err - } - } - - funderAddr, err := ak.AddressCodec().BytesToString(funder.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgFundCommunityPool{}), "unable to get funder address"), nil, err - } - msg := types.NewMsgFundCommunityPool(fundAmount, funderAddr) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txConfig, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: funder, - AccountKeeper: ak, - ModuleName: types.ModuleName, - } - - return simulation.GenAndDeliverTx(txCtx, fees) - } -} diff --git a/x/protocolpool/simulation/proposals.go b/x/protocolpool/simulation/proposals.go deleted file mode 100644 index 0afddda38f01..000000000000 --- a/x/protocolpool/simulation/proposals.go +++ /dev/null @@ -1,57 +0,0 @@ -package simulation - -import ( - "context" - "math/rand" - - coreaddress "cosmossdk.io/core/address" - pooltypes "cosmossdk.io/x/protocolpool/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -const ( - OpWeightMsgCommunityPoolSpend = "op_weight_msg_community_pool_spend" - - DefaultWeightMsgCommunityPoolSpend int = 50 -) - -func ProposalMsgs() []simtypes.WeightedProposalMsg { - return []simtypes.WeightedProposalMsg{ - simulation.NewWeightedProposalMsgX( - OpWeightMsgCommunityPoolSpend, - DefaultWeightMsgCommunityPoolSpend, - SimulateMsgCommunityPoolSpend, - ), - } -} - -func SimulateMsgCommunityPoolSpend(_ context.Context, r *rand.Rand, _ []simtypes.Account, cdc coreaddress.Codec) (sdk.Msg, error) { - // use the default gov module account address as authority - var authority sdk.AccAddress = address.Module("gov") - - accs := simtypes.RandomAccounts(r, 5) - acc, _ := simtypes.RandomAcc(r, accs) - - coins, err := sdk.ParseCoinsNormalized("100stake,2testtoken") - if err != nil { - return nil, err - } - - authorityAddr, err := cdc.BytesToString(authority) - if err != nil { - return nil, err - } - recipentAddr, err := cdc.BytesToString(acc.Address) - if err != nil { - return nil, err - } - return &pooltypes.MsgCommunityPoolSpend{ - Authority: authorityAddr, - Recipient: recipentAddr, - Amount: coins, - }, nil -} diff --git a/x/protocolpool/simulation/proposals_test.go b/x/protocolpool/simulation/proposals_test.go deleted file mode 100644 index b452b01970fc..000000000000 --- a/x/protocolpool/simulation/proposals_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package simulation_test - -import ( - "context" - "math/rand" - "testing" - - "gotest.tools/v3/assert" - - "cosmossdk.io/x/protocolpool/simulation" - pooltypes "cosmossdk.io/x/protocolpool/types" - - codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -func TestProposalMsgs(t *testing.T) { - // initialize parameters - s := rand.NewSource(1) - r := rand.New(s) - addressCodec := codectestutil.CodecOptions{}.GetAddressCodec() - accounts := simtypes.RandomAccounts(r, 3) - - // execute ProposalMsgs function - weightedProposalMsgs := simulation.ProposalMsgs() - assert.Assert(t, len(weightedProposalMsgs) == 1) - - w0 := weightedProposalMsgs[0] - - // tests w0 interface: - assert.Equal(t, simulation.OpWeightMsgCommunityPoolSpend, w0.AppParamsKey()) - assert.Equal(t, simulation.DefaultWeightMsgCommunityPoolSpend, w0.DefaultWeight()) - - msg, err := w0.MsgSimulatorFn()(context.Background(), r, accounts, addressCodec) - assert.NilError(t, err) - msgCommunityPoolSpend, ok := msg.(*pooltypes.MsgCommunityPoolSpend) - assert.Assert(t, ok) - - coins, err := sdk.ParseCoinsNormalized("100stake,2testtoken") - assert.NilError(t, err) - - authAddr, err := addressCodec.BytesToString(address.Module("gov")) - assert.NilError(t, err) - assert.Equal(t, authAddr, msgCommunityPoolSpend.Authority) - assert.Assert(t, msgCommunityPoolSpend.Amount.Equal(coins)) -} diff --git a/x/simulation/client/cli/flags.go b/x/simulation/client/cli/flags.go index 311ce7017d85..1dc05fa47f29 100644 --- a/x/simulation/client/cli/flags.go +++ b/x/simulation/client/cli/flags.go @@ -30,6 +30,7 @@ var ( FlagPeriodValue uint FlagGenesisTimeValue int64 FlagSigverifyTxValue bool + FlagFauxMerkle bool ) // GetSimulatorFlags gets the values of all the available simulation flags @@ -54,6 +55,7 @@ func GetSimulatorFlags() { flag.UintVar(&FlagPeriodValue, "Period", 0, "run slow invariants only once every period assertions") flag.Int64Var(&FlagGenesisTimeValue, "GenesisTime", time.Now().Unix(), "use current time as genesis UNIX time for default") flag.BoolVar(&FlagSigverifyTxValue, "SigverifyTx", true, "whether to sigverify check for transaction ") + flag.BoolVar(&FlagFauxMerkle, "FauxMerkle", false, "use faux merkle instead of iavl") } // NewConfigFromFlags creates a simulation from the retrieved values of the flags. @@ -73,5 +75,6 @@ func NewConfigFromFlags() simulation.Config { Lean: FlagLeanValue, Commit: FlagCommitValue, DBBackend: FlagDBBackendValue, + FauxMerkle: FlagFauxMerkle, } } diff --git a/x/simulation/log.go b/x/simulation/log.go index c1f9c439e10b..4547e8f96d05 100644 --- a/x/simulation/log.go +++ b/x/simulation/log.go @@ -76,7 +76,7 @@ func createLogFile(seed int64) *os.File { if err != nil { panic(err) } - fmt.Printf("Logs to writing to %s\n", filePath) + fmt.Printf("Logs to writing to %q\n", filePath) return f } diff --git a/x/simulation/operation.go b/x/simulation/operation.go index 8146ef15273a..e0cf06f3f173 100644 --- a/x/simulation/operation.go +++ b/x/simulation/operation.go @@ -76,7 +76,7 @@ func NewOperationQueue() OperationQueue { } // queueOperations adds all future operations into the operation queue. -func queueOperations(queuedOps OperationQueue, queuedTimeOps, futureOps []simulation.FutureOperation) { +func queueOperations(queuedOps OperationQueue, queuedTimeOps *[]simulation.FutureOperation, futureOps []simulation.FutureOperation) { if futureOps == nil { return } @@ -96,15 +96,15 @@ func queueOperations(queuedOps OperationQueue, queuedTimeOps, futureOps []simula // TODO: Replace with proper sorted data structure, so don't have the // copy entire slice index := sort.Search( - len(queuedTimeOps), + len(*queuedTimeOps), func(i int) bool { - return queuedTimeOps[i].BlockTime.After(futureOp.BlockTime) + return (*queuedTimeOps)[i].BlockTime.After(futureOp.BlockTime) }, ) - queuedTimeOps = append(queuedTimeOps, simulation.FutureOperation{}) - copy(queuedTimeOps[index+1:], queuedTimeOps[index:]) - queuedTimeOps[index] = futureOp + *queuedTimeOps = append(*queuedTimeOps, simulation.FutureOperation{}) + copy((*queuedTimeOps)[index+1:], (*queuedTimeOps)[index:]) + (*queuedTimeOps)[index] = futureOp } } diff --git a/x/simulation/params.go b/x/simulation/params.go index 4fce44cff82e..1c6032c867c1 100644 --- a/x/simulation/params.go +++ b/x/simulation/params.go @@ -185,8 +185,8 @@ func (w WeightedProposalContent) ContentSimulatorFn() simulation.ContentSimulato // Consensus Params -// randomConsensusParams returns random simulation consensus parameters, it extracts the Evidence from the Staking genesis state. -func randomConsensusParams(r *rand.Rand, appState json.RawMessage, cdc codec.JSONCodec, maxGas int64) *cmtproto.ConsensusParams { +// RandomConsensusParams returns random simulation consensus parameters, it extracts the Evidence from the Staking genesis state. +func RandomConsensusParams(r *rand.Rand, appState json.RawMessage, cdc codec.JSONCodec, maxGas int64) *cmtproto.ConsensusParams { var genesisState map[string]json.RawMessage err := json.Unmarshal(appState, &genesisState) if err != nil { diff --git a/x/simulation/params_test.go b/x/simulation/params_test.go index e4d6f380d101..1e05e3adadc8 100644 --- a/x/simulation/params_test.go +++ b/x/simulation/params_test.go @@ -1,6 +1,7 @@ package simulation import ( + "context" "fmt" "math/rand" "testing" @@ -29,7 +30,7 @@ func TestNewWeightedProposalContent(t *testing.T) { key := "theKey" weight := 1 content := &testContent{} - f := func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { //nolint:staticcheck // used for legacy testing + f := func(r *rand.Rand, ctx context.Context, accs []simtypes.Account) simtypes.Content { //nolint:staticcheck // used for legacy testing return content } diff --git a/x/simulation/simulate.go b/x/simulation/simulate.go index cd4fbe590799..9fec5b8c4ead 100644 --- a/x/simulation/simulate.go +++ b/x/simulation/simulate.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "math/rand" + "slices" "testing" "time" @@ -21,7 +22,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/simulation" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) const AverageBlockTime = 6 * time.Second @@ -30,18 +31,18 @@ const AverageBlockTime = 6 * time.Second func initChain( r *rand.Rand, params Params, - accounts []simulation.Account, + accounts []simtypes.Account, app *baseapp.BaseApp, - appStateFn simulation.AppStateFn, - config simulation.Config, + appStateFn simtypes.AppStateFn, + config simtypes.Config, cdc codec.JSONCodec, -) (mockValidators, time.Time, []simulation.Account, string) { +) (mockValidators, time.Time, []simtypes.Account, string) { blockMaxGas := int64(-1) if config.BlockMaxGas > 0 { blockMaxGas = config.BlockMaxGas } appState, accounts, chainID, genesisTimestamp := appStateFn(r, accounts, config) - consensusParams := randomConsensusParams(r, appState, cdc, blockMaxGas) + consensusParams := RandomConsensusParams(r, appState, cdc, blockMaxGas) req := abci.InitChainRequest{ AppStateBytes: appState, ChainId: chainID, @@ -64,17 +65,17 @@ func SimulateFromSeed( // exists for backwards compatibility only logger corelog.Logger, w io.Writer, app *baseapp.BaseApp, - appStateFn simulation.AppStateFn, - randAccFn simulation.RandomAccountFn, + appStateFn simtypes.AppStateFn, + randAccFn simtypes.RandomAccountFn, ops WeightedOperations, blockedAddrs map[string]bool, - config simulation.Config, + config simtypes.Config, cdc codec.JSONCodec, addressCodec address.Codec, ) (exportedParams Params, err error) { tb.Helper() mode, _, _ := getTestingMode(tb) - return SimulateFromSeedX(tb, logger, w, app, appStateFn, randAccFn, ops, blockedAddrs, config, cdc, addressCodec, NewLogWriter(mode)) + return SimulateFromSeedX(tb, logger, w, app, appStateFn, randAccFn, ops, blockedAddrs, config, cdc, NewLogWriter(mode)) } // SimulateFromSeedX tests an application by running the provided @@ -84,16 +85,20 @@ func SimulateFromSeedX( logger corelog.Logger, w io.Writer, app *baseapp.BaseApp, - appStateFn simulation.AppStateFn, - randAccFn simulation.RandomAccountFn, + appStateFn simtypes.AppStateFn, + randAccFn simtypes.RandomAccountFn, ops WeightedOperations, blockedAddrs map[string]bool, - config simulation.Config, + config simtypes.Config, cdc codec.JSONCodec, - addressCodec address.Codec, logWriter LogWriter, ) (exportedParams Params, err error) { tb.Helper() + defer func() { + if err != nil { + logWriter.PrintLogs() + } + }() // in case we have to end early, don't os.Exit so that we can run cleanup code. testingMode, _, b := getTestingMode(tb) @@ -120,25 +125,15 @@ func SimulateFromSeedX( config.ChainID = chainID // remove module account address if they exist in accs - var tmpAccs []simulation.Account - - for _, acc := range accs { - accAddr, err := addressCodec.BytesToString(acc.Address) - if err != nil { - return params, err - } - if !blockedAddrs[accAddr] { - tmpAccs = append(tmpAccs, acc) - } - } - - accs = tmpAccs + accs = slices.DeleteFunc(accs, func(acc simtypes.Account) bool { + return blockedAddrs[acc.AddressBech32] + }) nextValidators := validators var ( pastTimes []time.Time pastVoteInfos [][]abci.VoteInfo - timeOperationQueue []simulation.FutureOperation + timeOperationQueue []simtypes.FutureOperation blockHeight = int64(config.InitialBlockHeight) proposerAddress = validators.randomProposer(r) @@ -168,7 +163,7 @@ func SimulateFromSeedX( eventStats.Tally, ops, operationQueue, - timeOperationQueue, + &timeOperationQueue, logWriter, config, ) @@ -191,6 +186,10 @@ func SimulateFromSeedX( exportedParams = params } + if _, err := app.FinalizeBlock(finalizeBlockReq); err != nil { + return params, fmt.Errorf("block finalization failed at height %d: %w", blockHeight, err) + } + for blockHeight < int64(config.NumBlocks+config.InitialBlockHeight) { pastTimes = append(pastTimes, blockTime) pastVoteInfos = append(pastVoteInfos, finalizeBlockReq.DecidedLastCommit.Votes) @@ -219,15 +218,14 @@ func SimulateFromSeedX( tb, operationQueue, blockTime, int(blockHeight), r, app, ctx, accs, logWriter, eventStats.Tally, config.Lean, config.ChainID, ) - numQueuedTimeOpsRan, timeFutureOps := runQueuedTimeOperations(tb, - timeOperationQueue, int(blockHeight), blockTime, + &timeOperationQueue, int(blockHeight), blockTime, r, app, ctx, accs, logWriter, eventStats.Tally, config.Lean, config.ChainID, ) futureOps = append(futureOps, timeFutureOps...) - queueOperations(operationQueue, timeOperationQueue, futureOps) + queueOperations(operationQueue, &timeOperationQueue, futureOps) // run standard operations operations := blockSimulator(r, app, ctx, accs, cmtproto.Header{ @@ -237,7 +235,6 @@ func SimulateFromSeedX( ChainID: config.ChainID, }) opCount += operations + numQueuedOpsRan + numQueuedTimeOpsRan - blockHeight++ logWriter.AddEntry(EndBlockEntry(blockTime, blockHeight)) @@ -272,7 +269,6 @@ func SimulateFromSeedX( exportedParams = params } } - logger.Info("Simulation complete", "height", blockHeight, "block-time", blockTime, "opsCount", opCount, "run-time", time.Since(startTime), "app-hash", hex.EncodeToString(app.LastCommitID().Hash)) @@ -287,9 +283,9 @@ func SimulateFromSeedX( type blockSimFn func( r *rand.Rand, - app *baseapp.BaseApp, + app simtypes.AppEntrypoint, ctx sdk.Context, - accounts []simulation.Account, + accounts []simtypes.Account, header cmtproto.Header, ) (opCount int) @@ -297,8 +293,8 @@ type blockSimFn func( // parameters being passed every time, to minimize memory overhead. func createBlockSimulator(tb testing.TB, printProgress bool, w io.Writer, params Params, event func(route, op, evResult string), ops WeightedOperations, - operationQueue OperationQueue, timeOperationQueue []simulation.FutureOperation, - logWriter LogWriter, config simulation.Config, + operationQueue OperationQueue, timeOperationQueue *[]simtypes.FutureOperation, + logWriter LogWriter, config simtypes.Config, ) blockSimFn { tb.Helper() lastBlockSizeState := 0 // state for [4 * uniform distribution] @@ -306,7 +302,7 @@ func createBlockSimulator(tb testing.TB, printProgress bool, w io.Writer, params selectOp := ops.getSelectOpFn() return func( - r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []simulation.Account, header cmtproto.Header, + r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accounts []simtypes.Account, header cmtproto.Header, ) (opCount int) { _, _ = fmt.Fprintf( w, "\rSimulating... block %d/%d, operation %d/%d.", @@ -315,7 +311,7 @@ func createBlockSimulator(tb testing.TB, printProgress bool, w io.Writer, params lastBlockSizeState, blocksize = getBlockSize(r, params, lastBlockSizeState, config.BlockSize) type opAndR struct { - op simulation.Operation + op simtypes.Operation rand *rand.Rand } @@ -363,11 +359,11 @@ Comment: %s`, } } -func runQueuedOperations(tb testing.TB, queueOps map[int][]simulation.Operation, +func runQueuedOperations(tb testing.TB, queueOps map[int][]simtypes.Operation, blockTime time.Time, height int, r *rand.Rand, app *baseapp.BaseApp, - ctx sdk.Context, accounts []simulation.Account, logWriter LogWriter, + ctx sdk.Context, accounts []simtypes.Account, logWriter LogWriter, event func(route, op, evResult string), lean bool, chainID string, -) (numOpsRan int, allFutureOps []simulation.FutureOperation) { +) (numOpsRan int, allFutureOps []simtypes.FutureOperation) { tb.Helper() queuedOp, ok := queueOps[height] if !ok { @@ -375,11 +371,15 @@ func runQueuedOperations(tb testing.TB, queueOps map[int][]simulation.Operation, } // Keep all future operations - allFutureOps = make([]simulation.FutureOperation, 0) + allFutureOps = make([]simtypes.FutureOperation, 0) numOpsRan = len(queuedOp) for i := 0; i < numOpsRan; i++ { opMsg, futureOps, err := queuedOp[i](r, app, ctx, accounts, chainID) + if err != nil { + logWriter.PrintLogs() + tb.FailNow() + } if len(futureOps) > 0 { allFutureOps = append(allFutureOps, futureOps...) } @@ -387,49 +387,44 @@ func runQueuedOperations(tb testing.TB, queueOps map[int][]simulation.Operation, opMsg.LogEvent(event) if !lean || opMsg.OK { - logWriter.AddEntry((QueuedMsgEntry(blockTime, int64(height), opMsg))) + logWriter.AddEntry(QueuedMsgEntry(blockTime, int64(height), opMsg)) } - if err != nil { - logWriter.PrintLogs() - tb.FailNow() - } } delete(queueOps, height) return numOpsRan, allFutureOps } -func runQueuedTimeOperations(tb testing.TB, queueOps []simulation.FutureOperation, +func runQueuedTimeOperations(tb testing.TB, queueOps *[]simtypes.FutureOperation, height int, currentTime time.Time, r *rand.Rand, - app *baseapp.BaseApp, ctx sdk.Context, accounts []simulation.Account, + app *baseapp.BaseApp, ctx sdk.Context, accounts []simtypes.Account, logWriter LogWriter, event func(route, op, evResult string), lean bool, chainID string, -) (numOpsRan int, allFutureOps []simulation.FutureOperation) { +) (numOpsRan int, allFutureOps []simtypes.FutureOperation) { tb.Helper() // Keep all future operations - allFutureOps = make([]simulation.FutureOperation, 0) - numOpsRan = 0 - for len(queueOps) > 0 && currentTime.After(queueOps[0].BlockTime) { - opMsg, futureOps, err := queueOps[0].Op(r, app, ctx, accounts, chainID) + for len(*queueOps) > 0 && currentTime.After((*queueOps)[0].BlockTime) { + if qOp := (*queueOps)[0]; qOp.Op != nil { + opMsg, futureOps, err := qOp.Op(r, app, ctx, accounts, chainID) - opMsg.LogEvent(event) + opMsg.LogEvent(event) - if !lean || opMsg.OK { - logWriter.AddEntry(QueuedMsgEntry(currentTime, int64(height), opMsg)) - } + if !lean || opMsg.OK { + logWriter.AddEntry(QueuedMsgEntry(currentTime, int64(height), opMsg)) + } - if err != nil { - logWriter.PrintLogs() - tb.FailNow() - } + if err != nil { + logWriter.PrintLogs() + tb.Fatal(err) + } - if len(futureOps) > 0 { - allFutureOps = append(allFutureOps, futureOps...) + if len(futureOps) > 0 { + allFutureOps = append(allFutureOps, futureOps...) + } } - - queueOps = queueOps[1:] + *queueOps = slices.Delete(*queueOps, 0, 1) numOpsRan++ } diff --git a/x/simulation/simulate_test.go b/x/simulation/simulate_test.go new file mode 100644 index 000000000000..31fe943acfa0 --- /dev/null +++ b/x/simulation/simulate_test.go @@ -0,0 +1,57 @@ +package simulation + +import ( + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +func TestRunQueuedTimeOperations(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + ctx := sdk.Context{} + lw := NewLogWriter(true) + noopEvent := func(route, op, evResult string) {} + var acc []simtypes.Account + noOp := simtypes.FutureOperation{ + Op: func(gotR *rand.Rand, gotApp simtypes.AppEntrypoint, ctx sdk.Context, accounts []simtypes.Account, chainID string) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + return simtypes.OperationMsg{}, nil, nil + }, + } + futureOp := simtypes.FutureOperation{ + Op: func(gotR *rand.Rand, gotApp simtypes.AppEntrypoint, ctx sdk.Context, accounts []simtypes.Account, chainID string) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + return simtypes.OperationMsg{}, []simtypes.FutureOperation{noOp}, nil + }, + } + + specs := map[string]struct { + queueOps []simtypes.FutureOperation + expOps []simtypes.FutureOperation + }{ + "empty": {}, + "single": { + queueOps: []simtypes.FutureOperation{noOp}, + }, + "multi": { + queueOps: []simtypes.FutureOperation{noOp, noOp}, + }, + "future op": { + queueOps: []simtypes.FutureOperation{futureOp}, + expOps: []simtypes.FutureOperation{noOp}, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + expOps := len(spec.queueOps) + n, fOps := runQueuedTimeOperations(t, &spec.queueOps, 0, time.Now(), r, nil, ctx, acc, lw, noopEvent, false, "testing") + require.Equal(t, expOps, n) + assert.Empty(t, spec.queueOps) + assert.Len(t, fOps, len(spec.expOps)) // using len as equal fails with Go 1.23 now + }) + } +} diff --git a/x/slashing/module.go b/x/slashing/module.go index e210daf33af8..8fbd57939533 100644 --- a/x/slashing/module.go +++ b/x/slashing/module.go @@ -18,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/simsx" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) @@ -40,12 +41,9 @@ var ( // AppModule implements an application module for the slashing module. type AppModule struct { cdc codec.Codec - registry cdctypes.InterfaceRegistry cometService comet.Service keeper keeper.Keeper - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper stakingKeeper types.StakingKeeper } @@ -61,10 +59,7 @@ func NewAppModule( ) AppModule { return AppModule{ cdc: cdc, - registry: registry, keeper: keeper, - accountKeeper: ak, - bankKeeper: bk, stakingKeeper: sk, cometService: cs, } @@ -171,9 +166,9 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { simulation.RandomizedGenState(simState) } -// ProposalMsgs returns msgs used for governance proposals for simulations. -func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { - return simulation.ProposalMsgs() +// ProposalMsgsX returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_update_params", 100), simulation.MsgUpdateParamsFactory()) } // RegisterStoreDecoder registers a decoder for slashing module's types @@ -181,10 +176,8 @@ func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) } -// WeightedOperations returns the all the slashing module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return simulation.WeightedOperations( - am.registry, simState.AppParams, simState.Cdc, simState.TxConfig, - am.accountKeeper, am.bankKeeper, am.keeper, am.stakingKeeper, - ) +// WeightedOperationsX returns the all the slashing module operations with their respective weights. +func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Registry) { + // note: using old keys for backwards compatibility + reg.Add(weights.Get("msg_unjail", 20), simulation.MsgUnjailFactory(am.keeper, am.stakingKeeper)) } diff --git a/x/slashing/simulation/genesis.go b/x/slashing/simulation/genesis.go index 26ea1aeb4072..8316b06a7c20 100644 --- a/x/slashing/simulation/genesis.go +++ b/x/slashing/simulation/genesis.go @@ -1,8 +1,6 @@ package simulation import ( - "encoding/json" - "fmt" "math/rand" "time" @@ -71,10 +69,5 @@ func RandomizedGenState(simState *module.SimulationState) { slashingGenesis := types.NewGenesisState(params, []types.SigningInfo{}, []types.ValidatorMissedBlocks{}) - bz, err := json.MarshalIndent(&slashingGenesis, "", " ") - if err != nil { - panic(err) - } - fmt.Printf("Selected randomly generated slashing parameters:\n%s\n", bz) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(slashingGenesis) } diff --git a/x/slashing/simulation/msg_factory.go b/x/slashing/simulation/msg_factory.go new file mode 100644 index 000000000000..86a59544425c --- /dev/null +++ b/x/slashing/simulation/msg_factory.go @@ -0,0 +1,98 @@ +package simulation + +import ( + "context" + "errors" + "time" + + sdkmath "cosmossdk.io/math" + "cosmossdk.io/x/slashing/keeper" + "cosmossdk.io/x/slashing/types" + + "github.com/cosmos/cosmos-sdk/simsx" +) + +func MsgUnjailFactory(k keeper.Keeper, sk types.StakingKeeper) simsx.SimMsgFactoryX { + return simsx.NewSimMsgFactoryWithDeliveryResultHandler[*types.MsgUnjail](func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgUnjail, simsx.SimDeliveryResultHandler) { + allVals, err := sk.GetAllValidators(ctx) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil, nil + } + validator := simsx.OneOf(testData.Rand(), allVals) + if !validator.IsJailed() { + reporter.Skip("validator not jailed") + return nil, nil, nil + } + if validator.InvalidExRate() { + reporter.Skip("validator with invalid exchange rate") + return nil, nil, nil + } + + info, err := k.ValidatorSigningInfo.Get(ctx, must(validator.GetConsAddr())) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil, nil + } + valOperBz := must(sk.ValidatorAddressCodec().StringToBytes(validator.GetOperator())) + valOper := testData.GetAccountbyAccAddr(reporter, valOperBz) + if reporter.IsSkipped() { + return nil, nil, nil + } + + selfDel, err := sk.Delegation(ctx, valOper.Address, valOperBz) + if selfDel == nil || err != nil { + reporter.Skip("no self delegation") + return nil, nil, nil + } + var handler simsx.SimDeliveryResultHandler + // result should fail if: + // - validator cannot be unjailed due to tombstone + // - validator is still in jailed period + // - self delegation too low + if info.Tombstoned || + simsx.BlockTime(ctx).Before(info.JailedUntil) || + selfDel.GetShares().IsNil() || + validator.TokensFromShares(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) { + handler = func(err error) error { + if err == nil { + switch { + case info.Tombstoned: + return errors.New("validator should not have been unjailed if validator tombstoned") + case simsx.BlockTime(ctx).Before(info.JailedUntil): + return errors.New("validator unjailed while validator still in jail period") + case selfDel.GetShares().IsNil() || validator.TokensFromShares(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()): + return errors.New("validator unjailed even though self-delegation too low") + } + } + return nil + } + } + return []simsx.SimAccount{valOper}, types.NewMsgUnjail(validator.GetOperator()), handler + }) +} + +// MsgUpdateParamsFactory creates a gov proposal for param updates +func MsgUpdateParamsFactory() simsx.SimMsgFactoryFn[*types.MsgUpdateParams] { + return func(_ context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgUpdateParams) { + r := testData.Rand() + params := types.DefaultParams() + params.DowntimeJailDuration = time.Duration(r.Timestamp().UnixNano()) + params.SignedBlocksWindow = int64(r.IntInRange(1, 1000)) + params.MinSignedPerWindow = sdkmath.LegacyNewDecWithPrec(int64(r.IntInRange(1, 100)), 2) + params.SlashFractionDoubleSign = sdkmath.LegacyNewDecWithPrec(int64(r.IntInRange(1, 100)), 2) + params.SlashFractionDowntime = sdkmath.LegacyNewDecWithPrec(int64(r.IntInRange(1, 100)), 2) + + return nil, &types.MsgUpdateParams{ + Authority: testData.ModuleAccountAddress(reporter, "gov"), + Params: params, + } + } +} + +func must[T any](r T, err error) T { + if err != nil { + panic(err) + } + return r +} diff --git a/x/slashing/simulation/operations.go b/x/slashing/simulation/operations.go deleted file mode 100644 index e14cef141346..000000000000 --- a/x/slashing/simulation/operations.go +++ /dev/null @@ -1,159 +0,0 @@ -package simulation - -import ( - "errors" - "math/rand" - - "cosmossdk.io/x/slashing/keeper" - "cosmossdk.io/x/slashing/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/testutil" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// Simulation operation weights constants -const ( - OpWeightMsgUnjail = "op_weight_msg_unjail" - - DefaultWeightMsgUnjail = 100 -) - -// WeightedOperations returns all the operations from the module with their respective weights -func WeightedOperations( - registry codectypes.InterfaceRegistry, - appParams simtypes.AppParams, - cdc codec.JSONCodec, - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k keeper.Keeper, - sk types.StakingKeeper, -) simulation.WeightedOperations { - var weightMsgUnjail int - appParams.GetOrGenerate(OpWeightMsgUnjail, &weightMsgUnjail, nil, func(_ *rand.Rand) { - weightMsgUnjail = DefaultWeightMsgUnjail - }) - - return simulation.WeightedOperations{ - simulation.NewWeightedOperation( - weightMsgUnjail, - SimulateMsgUnjail(codec.NewProtoCodec(registry), txGen, ak, bk, k, sk), - ), - } -} - -// SimulateMsgUnjail generates a MsgUnjail with random values -func SimulateMsgUnjail( - cdc *codec.ProtoCodec, - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k keeper.Keeper, - sk types.StakingKeeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, - accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgUnjail{}) - - allVals, err := sk.GetAllValidators(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get all validators"), nil, err - } - - validator, ok := testutil.RandSliceElem(r, allVals) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is not ok"), nil, nil // skip - } - - bz, err := sk.ValidatorAddressCodec().StringToBytes(validator.GetOperator()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to convert validator address to bytes"), nil, err - } - - simAccount, found := simtypes.FindAccount(accs, sdk.AccAddress(bz)) - if !found { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to find account"), nil, nil // skip - } - - if !validator.IsJailed() { - // TODO: due to this condition this message is almost, if not always, skipped ! - return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is not jailed"), nil, nil - } - - consAddr, err := validator.GetConsAddr() - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validator consensus key"), nil, err - } - info, _ := k.ValidatorSigningInfo.Get(ctx, consAddr) - - selfDel, _ := sk.Delegation(ctx, simAccount.Address, bz) - if selfDel == nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "self delegation is nil"), nil, nil // skip - } - - account := ak.GetAccount(ctx, sdk.AccAddress(bz)) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - fees, err := simtypes.RandomFees(r, spendable) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate fees"), nil, nil - } - - msg := types.NewMsgUnjail(validator.GetOperator()) - - tx, err := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - simtestutil.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - simAccount.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate mock tx"), nil, err - } - - _, res, err := app.SimDeliver(txGen.TxEncoder(), tx) - - // result should fail if: - // - validator cannot be unjailed due to tombstone - // - validator is still in jailed period - // - self delegation too low - if info.Tombstoned || - ctx.HeaderInfo().Time.Before(info.JailedUntil) || - selfDel.GetShares().IsNil() || - validator.TokensFromShares(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) { - if res != nil && err == nil { - if info.Tombstoned { - return simtypes.NewOperationMsg(msg, true, ""), nil, errors.New("validator should not have been unjailed if validator tombstoned") - } - if ctx.HeaderInfo().Time.Before(info.JailedUntil) { - return simtypes.NewOperationMsg(msg, true, ""), nil, errors.New("validator unjailed while validator still in jail period") - } - if selfDel.GetShares().IsNil() || - validator.TokensFromShares(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) { - return simtypes.NewOperationMsg(msg, true, ""), nil, errors.New("validator unjailed even though self-delegation too low") - } - } - // msg failed as expected - return simtypes.NewOperationMsg(msg, false, ""), nil, nil - } - - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, errors.New(res.Log) - } - - return simtypes.NewOperationMsg(msg, true, ""), nil, nil - } -} diff --git a/x/staking/depinject.go b/x/staking/depinject.go index e1e7a5844960..28012965b4df 100644 --- a/x/staking/depinject.go +++ b/x/staking/depinject.go @@ -17,6 +17,7 @@ import ( "cosmossdk.io/x/staking/types" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simsx" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -80,7 +81,7 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { in.ConsensusAddressCodec, in.CometInfoService, ) - m := NewAppModule(in.Cdc, k, in.AccountKeeper, in.BankKeeper) + m := NewAppModule(in.Cdc, k) return ModuleOutputs{StakingKeeper: k, Module: m} } @@ -130,20 +131,23 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { simulation.RandomizedGenState(simState) } -// ProposalMsgs returns msgs used for governance proposals for simulations. -func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { - return simulation.ProposalMsgs() -} - // RegisterStoreDecoder registers a decoder for staking module's types func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) } -// WeightedOperations returns the all the staking module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return simulation.WeightedOperations( - simState.AppParams, simState.Cdc, simState.TxConfig, - am.accountKeeper, am.bankKeeper, am.keeper, - ) +// ProposalMsgsX returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_update_params", 100), simulation.MsgUpdateParamsFactory()) +} + +// WeightedOperationsX returns the all the staking module operations with their respective weights. +func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Registry) { + reg.Add(weights.Get("msg_create_validator", 100), simulation.MsgCreateValidatorFactory(am.keeper)) + reg.Add(weights.Get("msg_delegate", 100), simulation.MsgDelegateFactory(am.keeper)) + reg.Add(weights.Get("msg_undelegate", 100), simulation.MsgUndelegateFactory(am.keeper)) + reg.Add(weights.Get("msg_edit_validator", 5), simulation.MsgEditValidatorFactory(am.keeper)) + reg.Add(weights.Get("msg_begin_redelegate", 100), simulation.MsgBeginRedelegateFactory(am.keeper)) + reg.Add(weights.Get("msg_cancel_unbonding_delegation", 100), simulation.MsgCancelUnbondingDelegationFactory(am.keeper)) + reg.Add(weights.Get("msg_rotate_cons_pubkey", 100), simulation.MsgRotateConsPubKeyFactory(am.keeper)) } diff --git a/x/staking/go.mod b/x/staking/go.mod index 066f8a1f58bc..12f56e0f4707 100644 --- a/x/staking/go.mod +++ b/x/staking/go.mod @@ -26,7 +26,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 google.golang.org/grpc v1.66.2 google.golang.org/protobuf v1.34.2 - gotest.tools/v3 v3.5.1 ) require ( @@ -170,6 +169,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect + gotest.tools/v3 v3.5.1 // indirect ) replace github.com/cosmos/cosmos-sdk => ../../. diff --git a/x/staking/module.go b/x/staking/module.go index cddb3ba4c4d2..52b5313ed15b 100644 --- a/x/staking/module.go +++ b/x/staking/module.go @@ -43,24 +43,15 @@ var ( // AppModule implements an application module for the staking module. type AppModule struct { - cdc codec.Codec - keeper *keeper.Keeper - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper + cdc codec.Codec + keeper *keeper.Keeper } // NewAppModule creates a new AppModule object -func NewAppModule( - cdc codec.Codec, - keeper *keeper.Keeper, - ak types.AccountKeeper, - bk types.BankKeeper, -) AppModule { +func NewAppModule(cdc codec.Codec, keeper *keeper.Keeper) AppModule { return AppModule{ - cdc: cdc, - keeper: keeper, - accountKeeper: ak, - bankKeeper: bk, + cdc: cdc, + keeper: keeper, } } diff --git a/x/staking/simulation/genesis.go b/x/staking/simulation/genesis.go index da5f694941d3..9808c3f43086 100644 --- a/x/staking/simulation/genesis.go +++ b/x/staking/simulation/genesis.go @@ -1,8 +1,6 @@ package simulation import ( - "encoding/json" - "fmt" "math/rand" "time" @@ -108,11 +106,5 @@ func RandomizedGenState(simState *module.SimulationState) { } stakingGenesis := types.NewGenesisState(params, validators, delegations) - - bz, err := json.MarshalIndent(&stakingGenesis.Params, "", " ") - if err != nil { - panic(err) - } - fmt.Printf("Selected randomly generated staking parameters:\n%s\n", bz) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(stakingGenesis) } diff --git a/x/staking/simulation/msg_factory.go b/x/staking/simulation/msg_factory.go new file mode 100644 index 000000000000..31c35cddd507 --- /dev/null +++ b/x/staking/simulation/msg_factory.go @@ -0,0 +1,384 @@ +package simulation + +import ( + "context" + "slices" + "time" + + "cosmossdk.io/math" + "cosmossdk.io/x/staking/keeper" + "cosmossdk.io/x/staking/types" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/simsx" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func MsgCreateValidatorFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgCreateValidator] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgCreateValidator) { + r := testData.Rand() + withoutValidators := simsx.SimAccountFilterFn(func(a simsx.SimAccount) bool { + _, err := k.GetValidator(ctx, sdk.ValAddress(a.Address)) + return err != nil + }) + withoutConsAddrUsed := simsx.SimAccountFilterFn(func(a simsx.SimAccount) bool { + consPubKey := sdk.GetConsAddress(a.ConsKey.PubKey()) + _, err := k.GetValidatorByConsAddr(ctx, consPubKey) + return err != nil + }) + bondDenom := must(k.BondDenom(ctx)) + valOper := testData.AnyAccount(reporter, withoutValidators, withoutConsAddrUsed, simsx.WithDenomBalance(bondDenom)) + if reporter.IsSkipped() { + return nil, nil + } + + newPubKey := valOper.ConsKey.PubKey() + assertKeyUnused(ctx, reporter, k, newPubKey) + if reporter.IsSkipped() { + return nil, nil + } + + selfDelegation := valOper.LiquidBalance().RandSubsetCoin(reporter, bondDenom) + description := types.NewDescription( + r.StringN(10), + r.StringN(10), + r.StringN(10), + r.StringN(10), + r.StringN(10), + ) + + maxCommission := math.LegacyNewDecWithPrec(int64(r.IntInRange(0, 100)), 2) + commission := types.NewCommissionRates( + r.DecN(maxCommission), + maxCommission, + r.DecN(maxCommission), + ) + + addr := must(k.ValidatorAddressCodec().BytesToString(valOper.Address)) + msg, err := types.NewMsgCreateValidator(addr, newPubKey, selfDelegation, description, commission, math.OneInt()) + if err != nil { + reporter.Skip(err.Error()) + return nil, nil + } + + return []simsx.SimAccount{valOper}, msg + } +} + +func MsgDelegateFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgDelegate] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgDelegate) { + r := testData.Rand() + bondDenom := must(k.BondDenom(ctx)) + val := randomValidator(ctx, reporter, k, r) + if reporter.IsSkipped() { + return nil, nil + } + + if val.InvalidExRate() { + reporter.Skip("validator's invalid exchange rate") + return nil, nil + } + sender := testData.AnyAccount(reporter) + delegation := sender.LiquidBalance().RandSubsetCoin(reporter, bondDenom) + return []simsx.SimAccount{sender}, types.NewMsgDelegate(sender.AddressBech32, val.GetOperator(), delegation) + } +} + +func MsgUndelegateFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgUndelegate] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgUndelegate) { + r := testData.Rand() + bondDenom := must(k.BondDenom(ctx)) + val := randomValidator(ctx, reporter, k, r) + if reporter.IsSkipped() { + return nil, nil + } + + // select delegator and amount for undelegate + valAddr := must(k.ValidatorAddressCodec().StringToBytes(val.GetOperator())) + delegations := must(k.GetValidatorDelegations(ctx, valAddr)) + if delegations == nil { + reporter.Skip("no delegation entries") + return nil, nil + } + // get random delegator from validator + delegation := delegations[r.Intn(len(delegations))] + delAddr := delegation.GetDelegatorAddr() + delegator := testData.GetAccount(reporter, delAddr) + + if hasMaxUD := must(k.HasMaxUnbondingDelegationEntries(ctx, delegator.Address, valAddr)); hasMaxUD { + reporter.Skipf("max unbodings") + return nil, nil + } + + totalBond := val.TokensFromShares(delegation.GetShares()).TruncateInt() + if !totalBond.IsPositive() { + reporter.Skip("total bond is negative") + return nil, nil + } + + unbondAmt := must(r.PositiveSDKIntn(totalBond)) + msg := types.NewMsgUndelegate(delAddr, val.GetOperator(), sdk.NewCoin(bondDenom, unbondAmt)) + return []simsx.SimAccount{delegator}, msg + } +} + +func MsgEditValidatorFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgEditValidator] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgEditValidator) { + r := testData.Rand() + val := randomValidator(ctx, reporter, k, r) + if reporter.IsSkipped() { + return nil, nil + } + + newCommissionRate := r.DecN(val.Commission.MaxRate) + if err := val.Commission.ValidateNewRate(newCommissionRate, simsx.BlockTime(ctx)); err != nil { + // skip as the commission is invalid + reporter.Skip("invalid commission rate") + return nil, nil + } + valOpAddrBz := must(k.ValidatorAddressCodec().StringToBytes(val.GetOperator())) + valOper := testData.GetAccountbyAccAddr(reporter, valOpAddrBz) + d := types.NewDescription(r.StringN(10), r.StringN(10), r.StringN(10), r.StringN(10), r.StringN(10)) + + msg := types.NewMsgEditValidator(val.GetOperator(), d, &newCommissionRate, nil) + return []simsx.SimAccount{valOper}, msg + } +} + +func MsgBeginRedelegateFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgBeginRedelegate] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgBeginRedelegate) { + bondDenom := must(k.BondDenom(ctx)) + if !testData.IsSendEnabledDenom(bondDenom) { + reporter.Skip("bond denom send not enabled") + return nil, nil + } + + r := testData.Rand() + // select random validator as src + vals := must(k.GetAllValidators(ctx)) + if len(vals) < 2 { + reporter.Skip("insufficient number of validators") + return nil, nil + } + srcVal := simsx.OneOf(r, vals) + srcValOpAddrBz := must(k.ValidatorAddressCodec().StringToBytes(srcVal.GetOperator())) + delegations := must(k.GetValidatorDelegations(ctx, srcValOpAddrBz)) + if delegations == nil { + reporter.Skip("no delegations") + return nil, nil + } + // get random delegator from src validator + delegation := simsx.OneOf(r, delegations) + totalBond := srcVal.TokensFromShares(delegation.GetShares()).TruncateInt() + if !totalBond.IsPositive() { + reporter.Skip("total bond is negative") + return nil, nil + } + redAmount, err := r.PositiveSDKIntn(totalBond) + if err != nil || redAmount.IsZero() { + reporter.Skip("unable to generate positive amount") + return nil, nil + } + + // check if the shares truncate to zero + shares := must(srcVal.SharesFromTokens(redAmount)) + if srcVal.TokensFromShares(shares).TruncateInt().IsZero() { + reporter.Skip("shares truncate to zero") + return nil, nil + } + + // pick a random delegator + delAddr := delegation.GetDelegatorAddr() + delAddrBz := must(testData.AddressCodec().StringToBytes(delAddr)) + if hasRecRedel := must(k.HasReceivingRedelegation(ctx, delAddrBz, srcValOpAddrBz)); hasRecRedel { + reporter.Skip("receiving redelegation is not allowed") + return nil, nil + } + delegator := testData.GetAccountbyAccAddr(reporter, delAddrBz) + if reporter.IsSkipped() { + return nil, nil + } + + // get random destination validator + destVal := simsx.OneOf(r, vals) + if srcVal.Equal(&destVal) { + destVal = simsx.OneOf(r, slices.DeleteFunc(vals, func(v types.Validator) bool { return srcVal.Equal(&v) })) + } + if destVal.InvalidExRate() { + reporter.Skip("invalid delegation rate") + return nil, nil + } + + destAddrBz := must(k.ValidatorAddressCodec().StringToBytes(destVal.GetOperator())) + if hasMaxRedel := must(k.HasMaxRedelegationEntries(ctx, delAddrBz, srcValOpAddrBz, destAddrBz)); hasMaxRedel { + reporter.Skip("maximum redelegation entries reached") + return nil, nil + } + + msg := types.NewMsgBeginRedelegate( + delAddr, srcVal.GetOperator(), destVal.GetOperator(), + sdk.NewCoin(bondDenom, redAmount), + ) + return []simsx.SimAccount{delegator}, msg + } +} + +func MsgCancelUnbondingDelegationFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgCancelUnbondingDelegation] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgCancelUnbondingDelegation) { + r := testData.Rand() + val := randomValidator(ctx, reporter, k, r) + if reporter.IsSkipped() { + return nil, nil + } + if val.IsJailed() || val.InvalidExRate() { + reporter.Skip("validator is jailed") + return nil, nil + } + valOpAddrBz := must(k.ValidatorAddressCodec().StringToBytes(val.GetOperator())) + valOper := testData.GetAccountbyAccAddr(reporter, valOpAddrBz) + unbondingDelegation, err := k.GetUnbondingDelegation(ctx, valOper.Address, valOpAddrBz) + if err != nil { + reporter.Skip("no unbonding delegation") + return nil, nil + } + + // This is a temporary fix to make staking simulation pass. We should fetch + // the first unbondingDelegationEntry that matches the creationHeight, because + // currently the staking msgServer chooses the first unbondingDelegationEntry + // with the matching creationHeight. + // + // ref: https://github.com/cosmos/cosmos-sdk/issues/12932 + creationHeight := unbondingDelegation.Entries[r.Intn(len(unbondingDelegation.Entries))].CreationHeight + + var unbondingDelegationEntry types.UnbondingDelegationEntry + for _, entry := range unbondingDelegation.Entries { + if entry.CreationHeight == creationHeight { + unbondingDelegationEntry = entry + break + } + } + if unbondingDelegationEntry.CompletionTime.Before(simsx.BlockTime(ctx)) { + reporter.Skip("unbonding delegation is already processed") + return nil, nil + } + + if !unbondingDelegationEntry.Balance.IsPositive() { + reporter.Skip("delegator receiving balance is negative") + return nil, nil + } + cancelBondAmt := r.Amount(unbondingDelegationEntry.Balance) + if cancelBondAmt.IsZero() { + reporter.Skip("cancelBondAmt amount is zero") + return nil, nil + } + + msg := types.NewMsgCancelUnbondingDelegation( + valOper.AddressBech32, + val.GetOperator(), + unbondingDelegationEntry.CreationHeight, + sdk.NewCoin(must(k.BondDenom(ctx)), cancelBondAmt), + ) + + return []simsx.SimAccount{valOper}, msg + } +} + +func MsgRotateConsPubKeyFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgRotateConsPubKey] { + return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgRotateConsPubKey) { + r := testData.Rand() + val := randomValidator(ctx, reporter, k, r) + if reporter.IsSkipped() { + return nil, nil + } + if val.Status != types.Bonded || val.ConsensusPower(sdk.DefaultPowerReduction) == 0 { + reporter.Skip("validator not bonded.") + return nil, nil + } + valOpAddrBz := must(k.ValidatorAddressCodec().StringToBytes(val.GetOperator())) + valOper := testData.GetAccountbyAccAddr(reporter, valOpAddrBz) + otherAccount := testData.AnyAccount(reporter, simsx.ExcludeAddresses(valOper.AddressBech32)) + + consAddress := must(k.ConsensusAddressCodec().BytesToString(must(val.GetConsAddr()))) + accAddress := must(k.ConsensusAddressCodec().BytesToString(otherAccount.ConsKey.PubKey().Address())) + if consAddress == accAddress { + reporter.Skip("new pubkey and current pubkey should be different") + return nil, nil + } + if !valOper.LiquidBalance().BlockAmount(must(k.Params.Get(ctx)).KeyRotationFee) { + reporter.Skip("not enough balance to pay for key rotation fee") + return nil, nil + } + if err := k.ExceedsMaxRotations(ctx, valOpAddrBz); err != nil { + reporter.Skip("rotations limit reached within unbonding period") + return nil, nil + } + // check whether the new cons key associated with another validator + newConsAddr := sdk.ConsAddress(otherAccount.ConsKey.PubKey().Address()) + + if _, err := k.GetValidatorByConsAddr(ctx, newConsAddr); err == nil { + reporter.Skip("cons key already used") + return nil, nil + } + msg := must(types.NewMsgRotateConsPubKey(val.GetOperator(), otherAccount.ConsKey.PubKey())) + + // check if there's another key rotation for this same key in the same block + for _, r := range must(k.GetBlockConsPubKeyRotationHistory(ctx)) { + if r.NewConsPubkey.Compare(msg.NewPubkey) == 0 { + reporter.Skip("cons key already used in this block") + return nil, nil + } + } + return []simsx.SimAccount{valOper}, msg + } +} + +// MsgUpdateParamsFactory creates a gov proposal for param updates +func MsgUpdateParamsFactory() simsx.SimMsgFactoryFn[*types.MsgUpdateParams] { + return func(_ context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgUpdateParams) { + r := testData.Rand() + params := types.DefaultParams() + // do not modify denom or staking will break + params.HistoricalEntries = r.Uint32InRange(0, 1000) + params.MaxEntries = r.Uint32InRange(1, 1000) + params.MaxValidators = r.Uint32InRange(1, 1000) + params.UnbondingTime = time.Duration(r.Timestamp().UnixNano()) + // modifying commission rate can cause issues for proposals within the same block + // params.MinCommissionRate = r.DecN(sdkmath.LegacyNewDec(1)) + + return nil, &types.MsgUpdateParams{ + Authority: testData.ModuleAccountAddress(reporter, "gov"), + Params: params, + } + } +} + +func randomValidator(ctx context.Context, reporter simsx.SimulationReporter, k *keeper.Keeper, r *simsx.XRand) types.Validator { + vals, err := k.GetAllValidators(ctx) + if err != nil || len(vals) == 0 { + reporter.Skipf("unable to get validators or empty list: %s", err) + return types.Validator{} + } + return simsx.OneOf(r, vals) +} + +// skips execution if there's another key rotation for the same key in the same block +func assertKeyUnused(ctx context.Context, reporter simsx.SimulationReporter, k *keeper.Keeper, newPubKey cryptotypes.PubKey) { + allRotations, err := k.GetBlockConsPubKeyRotationHistory(ctx) + if err != nil { + reporter.Skipf("cannot get block cons key rotation history: %s", err.Error()) + return + } + for _, r := range allRotations { + if r.NewConsPubkey.Compare(newPubKey) != 0 { + reporter.Skip("cons key already used in this block") + return + } + } +} + +func must[T any](r T, err error) T { + if err != nil { + panic(err) + } + return r +} diff --git a/x/staking/simulation/operations.go b/x/staking/simulation/operations.go deleted file mode 100644 index 83ca16fb43c6..000000000000 --- a/x/staking/simulation/operations.go +++ /dev/null @@ -1,873 +0,0 @@ -package simulation - -import ( - "bytes" - "fmt" - "math/rand" - - "cosmossdk.io/math" - "cosmossdk.io/x/staking/keeper" - "cosmossdk.io/x/staking/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/testutil" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// Simulation operation weights constants -const ( - DefaultWeightMsgCreateValidator int = 100 - DefaultWeightMsgEditValidator int = 5 - DefaultWeightMsgDelegate int = 100 - DefaultWeightMsgUndelegate int = 100 - DefaultWeightMsgBeginRedelegate int = 100 - DefaultWeightMsgCancelUnbondingDelegation int = 100 - DefaultWeightMsgRotateConsPubKey int = 100 - - OpWeightMsgCreateValidator = "op_weight_msg_create_validator" - OpWeightMsgEditValidator = "op_weight_msg_edit_validator" - OpWeightMsgDelegate = "op_weight_msg_delegate" - OpWeightMsgUndelegate = "op_weight_msg_undelegate" - OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate" - OpWeightMsgCancelUnbondingDelegation = "op_weight_msg_cancel_unbonding_delegation" - OpWeightMsgRotateConsPubKey = "op_weight_msg_rotate_cons_pubkey" -) - -// WeightedOperations returns all the operations from the module with their respective weights -func WeightedOperations( - appParams simtypes.AppParams, - cdc codec.JSONCodec, - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, -) simulation.WeightedOperations { - var ( - weightMsgCreateValidator int - weightMsgEditValidator int - weightMsgDelegate int - weightMsgUndelegate int - weightMsgBeginRedelegate int - weightMsgCancelUnbondingDelegation int - weightMsgRotateConsPubKey int - ) - - appParams.GetOrGenerate(OpWeightMsgCreateValidator, &weightMsgCreateValidator, nil, func(_ *rand.Rand) { - weightMsgCreateValidator = DefaultWeightMsgCreateValidator - }) - - appParams.GetOrGenerate(OpWeightMsgEditValidator, &weightMsgEditValidator, nil, func(_ *rand.Rand) { - weightMsgEditValidator = DefaultWeightMsgEditValidator - }) - - appParams.GetOrGenerate(OpWeightMsgDelegate, &weightMsgDelegate, nil, func(_ *rand.Rand) { - weightMsgDelegate = DefaultWeightMsgDelegate - }) - - appParams.GetOrGenerate(OpWeightMsgUndelegate, &weightMsgUndelegate, nil, func(_ *rand.Rand) { - weightMsgUndelegate = DefaultWeightMsgUndelegate - }) - - appParams.GetOrGenerate(OpWeightMsgBeginRedelegate, &weightMsgBeginRedelegate, nil, func(_ *rand.Rand) { - weightMsgBeginRedelegate = DefaultWeightMsgBeginRedelegate - }) - - appParams.GetOrGenerate(OpWeightMsgCancelUnbondingDelegation, &weightMsgCancelUnbondingDelegation, nil, func(_ *rand.Rand) { - weightMsgCancelUnbondingDelegation = DefaultWeightMsgCancelUnbondingDelegation - }) - - appParams.GetOrGenerate(OpWeightMsgRotateConsPubKey, &weightMsgRotateConsPubKey, nil, func(_ *rand.Rand) { - weightMsgRotateConsPubKey = DefaultWeightMsgRotateConsPubKey - }) - - return simulation.WeightedOperations{ - simulation.NewWeightedOperation( - weightMsgCreateValidator, - SimulateMsgCreateValidator(txGen, ak, bk, k), - ), - simulation.NewWeightedOperation( - weightMsgEditValidator, - SimulateMsgEditValidator(txGen, ak, bk, k), - ), - simulation.NewWeightedOperation( - weightMsgDelegate, - SimulateMsgDelegate(txGen, ak, bk, k), - ), - simulation.NewWeightedOperation( - weightMsgUndelegate, - SimulateMsgUndelegate(txGen, ak, bk, k), - ), - simulation.NewWeightedOperation( - weightMsgBeginRedelegate, - SimulateMsgBeginRedelegate(txGen, ak, bk, k), - ), - simulation.NewWeightedOperation( - weightMsgCancelUnbondingDelegation, - SimulateMsgCancelUnbondingDelegate(txGen, ak, bk, k), - ), - simulation.NewWeightedOperation( - weightMsgRotateConsPubKey, - SimulateMsgRotateConsPubKey(txGen, ak, bk, k), - ), - } -} - -// SimulateMsgCreateValidator generates a MsgCreateValidator with random values -func SimulateMsgCreateValidator( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgCreateValidator{}) - - simAccount, _ := simtypes.RandomAcc(r, accs) - address := sdk.ValAddress(simAccount.Address) - - // ensure the validator doesn't exist already - _, err := k.GetValidator(ctx, address) - if err == nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "validator already exists"), nil, nil - } - - consPubKey := sdk.GetConsAddress(simAccount.ConsKey.PubKey()) - _, err = k.GetValidatorByConsAddr(ctx, consPubKey) - if err == nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "cons key already used"), nil, nil - } - - denom, err := k.BondDenom(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err - } - - balance := bk.GetBalance(ctx, simAccount.Address, denom).Amount - if !balance.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, msgType, "balance is negative"), nil, nil - } - - amount, err := simtypes.RandPositiveInt(r, balance) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate positive amount"), nil, err - } - - selfDelegation := sdk.NewCoin(denom, amount) - - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - var fees sdk.Coins - - coins, hasNeg := spendable.SafeSub(selfDelegation) - if !hasNeg { - fees, err = simtypes.RandomFees(r, coins) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate fees"), nil, err - } - } - - description := types.NewDescription( - simtypes.RandStringOfLength(r, 10), - simtypes.RandStringOfLength(r, 10), - simtypes.RandStringOfLength(r, 10), - simtypes.RandStringOfLength(r, 10), - simtypes.RandStringOfLength(r, 10), - ) - - maxCommission := math.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 100)), 2) - commission := types.NewCommissionRates( - simtypes.RandomDecAmount(r, maxCommission), - maxCommission, - simtypes.RandomDecAmount(r, maxCommission), - ) - - addr, err := k.ValidatorAddressCodec().BytesToString(address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate validator address"), nil, err - } - - msg, err := types.NewMsgCreateValidator(addr, simAccount.ConsKey.PubKey(), selfDelegation, description, commission, math.OneInt()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to create CreateValidator message"), nil, err - } - - // check if there's another key rotation for this same key in the same block - allRotations, err := k.GetBlockConsPubKeyRotationHistory(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "cannot get block cons key rotation history"), nil, err - } - for _, r := range allRotations { - if r.NewConsPubkey.Compare(msg.Pubkey) == 0 { - return simtypes.NoOpMsg(types.ModuleName, msgType, "cons key already used in this block"), nil, nil - } - } - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txGen, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - ModuleName: types.ModuleName, - } - - return simulation.GenAndDeliverTx(txCtx, fees) - } -} - -// SimulateMsgEditValidator generates a MsgEditValidator with random values -func SimulateMsgEditValidator( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgEditValidator{}) - - vals, err := k.GetAllValidators(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err - } - - if len(vals) == 0 { - return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil - } - - val, ok := testutil.RandSliceElem(r, vals) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick a validator"), nil, nil - } - - address := val.GetOperator() - newCommissionRate := simtypes.RandomDecAmount(r, val.Commission.MaxRate) - - if err := val.Commission.ValidateNewRate(newCommissionRate, ctx.HeaderInfo().Time); err != nil { - // skip as the commission is invalid - return simtypes.NoOpMsg(types.ModuleName, msgType, "invalid commission rate"), nil, nil - } - - bz, err := k.ValidatorAddressCodec().StringToBytes(val.GetOperator()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err - } - - simAccount, found := simtypes.FindAccount(accs, sdk.AccAddress(bz)) - if !found { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to find account"), nil, fmt.Errorf("validator %s not found", val.GetOperator()) - } - - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - description := types.NewDescription( - simtypes.RandStringOfLength(r, 10), - simtypes.RandStringOfLength(r, 10), - simtypes.RandStringOfLength(r, 10), - simtypes.RandStringOfLength(r, 10), - simtypes.RandStringOfLength(r, 10), - ) - - msg := types.NewMsgEditValidator(address, description, &newCommissionRate, nil) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txGen, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// SimulateMsgDelegate generates a MsgDelegate with random values -func SimulateMsgDelegate( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgDelegate{}) - denom, err := k.BondDenom(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err - } - - vals, err := k.GetAllValidators(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err - } - - if len(vals) == 0 { - return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil - } - - simAccount, _ := simtypes.RandomAcc(r, accs) - val, ok := testutil.RandSliceElem(r, vals) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick a validator"), nil, nil - } - - if val.InvalidExRate() { - return simtypes.NoOpMsg(types.ModuleName, msgType, "validator's invalid echange rate"), nil, nil - } - - amount := bk.GetBalance(ctx, simAccount.Address, denom).Amount - if !amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, msgType, "balance is negative"), nil, nil - } - - amount, err = simtypes.RandPositiveInt(r, amount) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate positive amount"), nil, err - } - - bondAmt := sdk.NewCoin(denom, amount) - - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - var fees sdk.Coins - - coins, hasNeg := spendable.SafeSub(bondAmt) - if !hasNeg { - fees, err = simtypes.RandomFees(r, coins) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate fees"), nil, err - } - } - - accAddr, err := ak.AddressCodec().BytesToString(simAccount.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting account string address"), nil, err - } - msg := types.NewMsgDelegate(accAddr, val.GetOperator(), bondAmt) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txGen, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - ModuleName: types.ModuleName, - } - - return simulation.GenAndDeliverTx(txCtx, fees) - } -} - -// SimulateMsgUndelegate generates a MsgUndelegate with random values -func SimulateMsgUndelegate( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgUndelegate{}) - - vals, err := k.GetAllValidators(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err - } - - if len(vals) == 0 { - return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil - } - - val, ok := testutil.RandSliceElem(r, vals) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is not ok"), nil, nil - } - - valAddr, err := k.ValidatorAddressCodec().StringToBytes(val.GetOperator()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err - } - delegations, err := k.GetValidatorDelegations(ctx, valAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator delegations"), nil, nil - } - - if delegations == nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "keeper does have any delegation entries"), nil, nil - } - - // get random delegator from validator - delegation := delegations[r.Intn(len(delegations))] - delAddr := delegation.GetDelegatorAddr() - - delAddrBz, err := ak.AddressCodec().StringToBytes(delAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting delegator address bytes"), nil, err - } - - hasMaxUD, err := k.HasMaxUnbondingDelegationEntries(ctx, delAddrBz, valAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting max unbonding delegation entries"), nil, err - } - - if hasMaxUD { - return simtypes.NoOpMsg(types.ModuleName, msgType, "keeper does have a max unbonding delegation entries"), nil, nil - } - - totalBond := val.TokensFromShares(delegation.GetShares()).TruncateInt() - if !totalBond.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, msgType, "total bond is negative"), nil, nil - } - - unbondAmt, err := simtypes.RandPositiveInt(r, totalBond) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "invalid unbond amount"), nil, err - } - - if unbondAmt.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unbond amount is zero"), nil, nil - } - - bondDenom, err := k.BondDenom(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err - } - - msg := types.NewMsgUndelegate( - delAddr, val.GetOperator(), sdk.NewCoin(bondDenom, unbondAmt), - ) - - if !bk.IsSendEnabledDenom(ctx, bondDenom) { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "bond denom send not enabled"), nil, nil - } - - // need to retrieve the simulation account associated with delegation to retrieve PrivKey - var simAccount simtypes.Account - - for _, simAcc := range accs { - if simAcc.Address.Equals(sdk.AccAddress(delAddrBz)) { - simAccount = simAcc - break - } - } - // if simaccount.PrivKey == nil, delegation address does not exist in accs. However, since smart contracts and module accounts can stake, we can ignore the error - if simAccount.PrivKey == nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "account private key is nil"), nil, nil - } - - account := ak.GetAccount(ctx, delAddrBz) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txGen, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// SimulateMsgCancelUnbondingDelegate generates a MsgCancelUnbondingDelegate with random values -func SimulateMsgCancelUnbondingDelegate( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgCancelUnbondingDelegation{}) - - vals, err := k.GetAllValidators(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err - } - - if len(vals) == 0 { - return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil - } - - simAccount, _ := simtypes.RandomAcc(r, accs) - val, ok := testutil.RandSliceElem(r, vals) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is not ok"), nil, nil - } - - if val.IsJailed() || val.InvalidExRate() { - return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is jailed"), nil, nil - } - - valAddr, err := k.ValidatorAddressCodec().StringToBytes(val.GetOperator()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err - } - unbondingDelegation, err := k.GetUnbondingDelegation(ctx, simAccount.Address, valAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "account does have any unbonding delegation"), nil, nil - } - - // This is a temporary fix to make staking simulation pass. We should fetch - // the first unbondingDelegationEntry that matches the creationHeight, because - // currently the staking msgServer chooses the first unbondingDelegationEntry - // with the matching creationHeight. - // - // ref: https://github.com/cosmos/cosmos-sdk/issues/12932 - creationHeight := unbondingDelegation.Entries[r.Intn(len(unbondingDelegation.Entries))].CreationHeight - - var unbondingDelegationEntry types.UnbondingDelegationEntry - - for _, entry := range unbondingDelegation.Entries { - if entry.CreationHeight == creationHeight { - unbondingDelegationEntry = entry - break - } - } - - if unbondingDelegationEntry.CompletionTime.Before(ctx.HeaderInfo().Time) { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unbonding delegation is already processed"), nil, nil - } - - if !unbondingDelegationEntry.Balance.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, msgType, "delegator receiving balance is negative"), nil, nil - } - - cancelBondAmt := simtypes.RandomAmount(r, unbondingDelegationEntry.Balance) - - if cancelBondAmt.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, msgType, "cancelBondAmt amount is zero"), nil, nil - } - - bondDenom, err := k.BondDenom(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err - } - - accAddr, err := ak.AddressCodec().BytesToString(simAccount.Address) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting account string address"), nil, err - } - msg := types.NewMsgCancelUnbondingDelegation( - accAddr, val.GetOperator(), unbondingDelegationEntry.CreationHeight, sdk.NewCoin(bondDenom, cancelBondAmt), - ) - - spendable := bk.SpendableCoins(ctx, simAccount.Address) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txGen, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// SimulateMsgBeginRedelegate generates a MsgBeginRedelegate with random values -func SimulateMsgBeginRedelegate( - txGen client.TxConfig, - ak types.AccountKeeper, - bk types.BankKeeper, - k *keeper.Keeper, -) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgBeginRedelegate{}) - - allVals, err := k.GetAllValidators(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err - } - - if len(allVals) == 0 { - return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil - } - - srcVal, ok := testutil.RandSliceElem(r, allVals) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick validator"), nil, nil - } - - srcAddr, err := k.ValidatorAddressCodec().StringToBytes(srcVal.GetOperator()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err - } - delegations, err := k.GetValidatorDelegations(ctx, srcAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator delegations"), nil, nil - } - - if delegations == nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "keeper does have any delegation entries"), nil, nil - } - - // get random delegator from src validator - delegation := delegations[r.Intn(len(delegations))] - delAddr := delegation.GetDelegatorAddr() - - delAddrBz, err := ak.AddressCodec().StringToBytes(delAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting delegator address bytes"), nil, err - } - - hasRecRedel, err := k.HasReceivingRedelegation(ctx, delAddrBz, srcAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting receiving redelegation"), nil, err - } - - if hasRecRedel { - return simtypes.NoOpMsg(types.ModuleName, msgType, "receveing redelegation is not allowed"), nil, nil // skip - } - - // get random destination validator - destVal, ok := testutil.RandSliceElem(r, allVals) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick validator"), nil, nil - } - - destAddr, err := k.ValidatorAddressCodec().StringToBytes(destVal.GetOperator()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err - } - hasMaxRedel, err := k.HasMaxRedelegationEntries(ctx, delAddrBz, srcAddr, destAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting max redelegation entries"), nil, err - } - - if bytes.Equal(srcAddr, destAddr) || destVal.InvalidExRate() || hasMaxRedel { - return simtypes.NoOpMsg(types.ModuleName, msgType, "checks failed"), nil, nil - } - - totalBond := srcVal.TokensFromShares(delegation.GetShares()).TruncateInt() - if !totalBond.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, msgType, "total bond is negative"), nil, nil - } - - redAmt, err := simtypes.RandPositiveInt(r, totalBond) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate positive amount"), nil, err - } - - if redAmt.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, msgType, "amount is zero"), nil, nil - } - - // check if the shares truncate to zero - shares, err := srcVal.SharesFromTokens(redAmt) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "invalid shares"), nil, err - } - - if srcVal.TokensFromShares(shares).TruncateInt().IsZero() { - return simtypes.NoOpMsg(types.ModuleName, msgType, "shares truncate to zero"), nil, nil // skip - } - - // need to retrieve the simulation account associated with delegation to retrieve PrivKey - var simAccount simtypes.Account - - for _, simAcc := range accs { - if simAcc.Address.Equals(sdk.AccAddress(delAddrBz)) { - simAccount = simAcc - break - } - } - - // if simaccount.PrivKey == nil, delegation address does not exist in accs. However, since smart contracts and module accounts can stake, we can ignore the error - if simAccount.PrivKey == nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "account private key is nil"), nil, nil - } - - account := ak.GetAccount(ctx, delAddrBz) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - bondDenom, err := k.BondDenom(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err - } - - if !bk.IsSendEnabledDenom(ctx, bondDenom) { - return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom send not enabled"), nil, nil - } - - msg := types.NewMsgBeginRedelegate( - delAddr, srcVal.GetOperator(), destVal.GetOperator(), - sdk.NewCoin(bondDenom, redAmt), - ) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txGen, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -func SimulateMsgRotateConsPubKey(txGen client.TxConfig, ak types.AccountKeeper, bk types.BankKeeper, k *keeper.Keeper) simtypes.Operation { - return func( - r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - msgType := sdk.MsgTypeURL(&types.MsgRotateConsPubKey{}) - - vals, err := k.GetAllValidators(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err - } - - if len(vals) == 0 { - return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil - } - - val, ok := testutil.RandSliceElem(r, vals) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick a validator"), nil, nil - } - - if val.Status != types.Bonded || val.ConsensusPower(sdk.DefaultPowerReduction) == 0 { - return simtypes.NoOpMsg(types.ModuleName, msgType, "validator not bonded."), nil, nil - } - - valAddr := val.GetOperator() - valBytes, err := k.ValidatorAddressCodec().StringToBytes(valAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err - } - - simAccount, found := simtypes.FindAccount(accs, sdk.AccAddress(valBytes)) - if !found { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to find account"), nil, fmt.Errorf("validator %s not found", val.GetOperator()) - } - - cons, err := val.GetConsAddr() - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "cannot get conskey"), nil, err - } - consAddress, err := k.ConsensusAddressCodec().BytesToString(cons) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting consensus address"), nil, err - } - - acc, _ := simtypes.RandomAcc(r, accs) - accAddress, err := k.ConsensusAddressCodec().BytesToString(acc.ConsKey.PubKey().Address()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting consensus address"), nil, err - } - if consAddress == accAddress { - return simtypes.NoOpMsg(types.ModuleName, msgType, "new pubkey and current pubkey should be different"), nil, nil - } - - account := ak.GetAccount(ctx, simAccount.Address) - if account == nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to find account"), nil, nil - } - - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - params, err := k.Params.Get(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "cannot get params"), nil, err - } - - if !spendable.IsAllGTE(sdk.NewCoins(params.KeyRotationFee)) { - return simtypes.NoOpMsg(types.ModuleName, msgType, "not enough balance to pay fee"), nil, nil - } - - if err := k.ExceedsMaxRotations(ctx, valBytes); err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "rotations limit reached within unbonding period"), nil, nil - } - - _, err = k.GetValidatorByConsAddr(ctx, cons) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "cannot get validator"), nil, err - } - - // check whether the new cons key associated with another validator - newConsAddr := sdk.ConsAddress(acc.ConsKey.PubKey().Address()) - _, err = k.GetValidatorByConsAddr(ctx, newConsAddr) - if err == nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "cons key already used"), nil, nil - } - - msg, err := types.NewMsgRotateConsPubKey(valAddr, acc.ConsKey.PubKey()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to build msg"), nil, err - } - - // check if there's another key rotation for this same key in the same block - allRotations, err := k.GetBlockConsPubKeyRotationHistory(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msgType, "cannot get block cons key rotation history"), nil, err - } - for _, r := range allRotations { - if r.NewConsPubkey.Compare(msg.NewPubkey) == 0 { - return simtypes.NoOpMsg(types.ModuleName, msgType, "cons key already used in this block"), nil, nil - } - } - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: txGen, - Cdc: nil, - Msg: msg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} diff --git a/x/staking/simulation/proposals.go b/x/staking/simulation/proposals.go deleted file mode 100644 index 36afe6db90d3..000000000000 --- a/x/staking/simulation/proposals.go +++ /dev/null @@ -1,56 +0,0 @@ -package simulation - -import ( - "context" - "math/rand" - "time" - - coreaddress "cosmossdk.io/core/address" - "cosmossdk.io/x/staking/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// Simulation operation weights constants -const ( - DefaultWeightMsgUpdateParams int = 100 - - OpWeightMsgUpdateParams = "op_weight_msg_update_params" -) - -// ProposalMsgs defines the module weighted proposals' contents -func ProposalMsgs() []simtypes.WeightedProposalMsg { - return []simtypes.WeightedProposalMsg{ - simulation.NewWeightedProposalMsgX( - OpWeightMsgUpdateParams, - DefaultWeightMsgUpdateParams, - SimulateMsgUpdateParams, - ), - } -} - -// SimulateMsgUpdateParams returns a random MsgUpdateParams -func SimulateMsgUpdateParams(_ context.Context, r *rand.Rand, _ []simtypes.Account, addressCodec coreaddress.Codec) (sdk.Msg, error) { - // use the default gov module account address as authority - var authority sdk.AccAddress = address.Module("gov") - - params := types.DefaultParams() - params.HistoricalEntries = uint32(simtypes.RandIntBetween(r, 0, 1000)) - params.MaxEntries = uint32(simtypes.RandIntBetween(r, 1, 1000)) - params.MaxValidators = uint32(simtypes.RandIntBetween(r, 1, 1000)) - params.UnbondingTime = time.Duration(simtypes.RandTimestamp(r).UnixNano()) - // changes to MinCommissionRate or BondDenom create issues for in flight messages or state operations - - addr, err := addressCodec.BytesToString(authority) - if err != nil { - return nil, err - } - - return &types.MsgUpdateParams{ - Authority: addr, - Params: params, - }, nil -} diff --git a/x/staking/simulation/proposals_test.go b/x/staking/simulation/proposals_test.go deleted file mode 100644 index 4373ae15b290..000000000000 --- a/x/staking/simulation/proposals_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package simulation_test - -import ( - "context" - "math/rand" - "testing" - - "gotest.tools/v3/assert" - - "cosmossdk.io/x/staking/simulation" - "cosmossdk.io/x/staking/types" - - codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" - "github.com/cosmos/cosmos-sdk/types/address" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -func TestProposalMsgs(t *testing.T) { - // initialize parameters - s := rand.NewSource(1) - r := rand.New(s) - - accounts := simtypes.RandomAccounts(r, 3) - addressCodec := codectestutil.CodecOptions{}.GetAddressCodec() - // execute ProposalMsgs function - weightedProposalMsgs := simulation.ProposalMsgs() - assert.Assert(t, len(weightedProposalMsgs) == 1) - - w0 := weightedProposalMsgs[0] - - // tests w0 interface: - assert.Equal(t, simulation.OpWeightMsgUpdateParams, w0.AppParamsKey()) - assert.Equal(t, simulation.DefaultWeightMsgUpdateParams, w0.DefaultWeight()) - - msg, err := w0.MsgSimulatorFn()(context.Background(), r, accounts, addressCodec) - assert.NilError(t, err) - msgUpdateParams, ok := msg.(*types.MsgUpdateParams) - assert.Assert(t, ok) - - addr, err := addressCodec.BytesToString(address.Module("gov")) - assert.NilError(t, err) - - assert.Equal(t, addr, msgUpdateParams.Authority) - assert.Equal(t, "stake", msgUpdateParams.Params.BondDenom) - assert.Equal(t, uint32(905), msgUpdateParams.Params.MaxEntries) - assert.Equal(t, uint32(540), msgUpdateParams.Params.HistoricalEntries) - assert.Equal(t, uint32(151), msgUpdateParams.Params.MaxValidators) - assert.Equal(t, "2417694h42m25s", msgUpdateParams.Params.UnbondingTime.String()) -}