feat(sims): Add sims2 framework and factory methods (backport #21613) (#21752)

Co-authored-by: Alexander Peters <alpe@users.noreply.github.com>
Co-authored-by: Julien Robert <julien@rbrt.fr>
This commit is contained in:
mergify[bot] 2024-09-17 10:04:02 +00:00 committed by GitHub
parent 391d4d72fb
commit 8823508147
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
140 changed files with 4900 additions and 9915 deletions

View File

@ -44,6 +44,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i
* (genutil) [#21701](https://github.com/cosmos/cosmos-sdk/pull/21701) Improved error messages for genesis validation.
* (runtime) [#21704](https://github.com/cosmos/cosmos-sdk/pull/21704) Move `upgradetypes.StoreLoader` to runtime and alias it in upgrade for backward compatibility.
* (sims)[#21613](https://github.com/cosmos/cosmos-sdk/pull/21613) Add sims2 framework and factory methods for simpler message factories in modules
## [v0.52.0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.52.0) - 2024-XX-XX

View File

@ -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),
)

3
go.mod
View File

@ -65,8 +65,6 @@ require (
sigs.k8s.io/yaml v1.4.0
)
require github.com/tidwall/btree v1.7.0 // indirect
require (
buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.34.2-20240130113600-88ef6483f90f.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
@ -154,6 +152,7 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect
github.com/supranational/blst v0.3.13 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v0.14.3 // indirect
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect

View File

@ -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=<path-to-plugin-binary>
# make test-sim-benchmark-streaming
#
# Using the built-in examples:
# export COSMOS_SDK_ABCI_V1=<path-to-sdk>/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=<path-to-plugin-binary>
# make test-sim-profile-streaming
#
# Using the built-in examples:
# export COSMOS_SDK_ABCI_V1=<path-to-sdk>/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

View File

@ -461,15 +461,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),

View File

@ -3,92 +3,18 @@
package simapp
import (
"os"
"testing"
"cosmossdk.io/log"
"github.com/cosmos/cosmos-sdk/testutils/sims"
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"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"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/simsx"
simcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli"
"testing"
)
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
config.ChainID = simsx.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)
}
if config.Commit {
db, ok := db.(simtestutil.DBStatsInterface)
if ok {
simtestutil.PrintStats(db)
}
}
simsx.RunWithSeed(b, config, NewSimApp, setupStateFactory, 1, nil)
}

View File

@ -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
}

42
simsx/README.md Normal file
View File

@ -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.

161
simsx/common_test.go Normal file
View File

@ -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
}

17
simsx/context.go Normal file
View File

@ -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)
}

98
simsx/delivery.go Normal file
View File

@ -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()
}

74
simsx/delivery_test.go Normal file
View File

@ -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)
})
}
}

458
simsx/environment.go Normal file
View File

@ -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)
}

67
simsx/environment_test.go Normal file
View File

@ -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())
}

151
simsx/msg_factory.go Normal file
View File

@ -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)
}
}

97
simsx/msg_factory_test.go Normal file
View File

@ -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())
})
}
}

52
simsx/params.go Normal file
View File

@ -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
})
}

206
simsx/registry.go Normal file
View File

@ -0,0 +1,206 @@
package simsx
import (
"context"
"iter"
"maps"
"math/rand"
"slices"
"strings"
"time"
"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)
sortedWeightedFactory := slices.SortedFunc(x, func(a, b WeightedFactory) int {
return a.Compare(b)
})
return func(yield func(uint32, SimMsgFactoryX) bool) {
for _, v := range sortedWeightedFactory {
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()))
}
}

158
simsx/registry_test.go Normal file
View File

@ -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))
})
}
}

257
simsx/reporter.go Normal file
View File

@ -0,0 +1,257 @@
package simsx
import (
"errors"
"fmt"
"maps"
"slices"
"strings"
"sync"
"sync/atomic"
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 := slices.Sorted(maps.Keys(s.counts))
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 {
values := maps.Values(c)
keys := maps.Keys(c)
sb.WriteString(fmt.Sprintf("%d\t%s: %q\n", sum(slices.Collect(values)), m, slices.Collect(keys)))
}
return sb.String()
}
func sum(values []int) int {
var r int
for _, v := range values {
r += v
}
return r
}

219
simsx/reporter_test.go Normal file
View File

@ -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)
})
}
}

377
simsx/runner.go Normal file
View File

@ -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()
}

38
simsx/slices.go Normal file
View File

@ -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))]
}

40
simsx/slices_test.go Normal file
View File

@ -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
}

View File

@ -13,7 +13,7 @@ require (
cosmossdk.io/store v1.1.1-0.20240909133312-50288938d1b6 // main
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.14.0 // main
cosmossdk.io/x/upgrade v0.0.0-20230613133644-0a778132a60f

View File

@ -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)...)
}

View File

@ -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)

View File

@ -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)

View File

@ -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),
)

View File

@ -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)

View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

@ -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))
}

View File

@ -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))
}

View File

@ -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))
}

View File

@ -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(),
)

View File

@ -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))
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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(),
)

View File

@ -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))
}

View File

@ -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(),
)

View File

@ -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
}

View File

@ -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(),
)

View File

@ -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
}

View File

@ -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

View File

@ -2,8 +2,9 @@ package sims
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"errors"
"io"
"math/rand"
"os"
@ -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, fmt.Errorf("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 *genesis, newAccs, nil
return newAccs, nil
}
func generateGenesisStates(modules []module.AppModuleSimulation, simState *module.SimulationState) {
for _, m := range modules {
m.GenerateGenesisState(simState)
}
}

View File

@ -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()
}

View File

@ -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
}
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
}
// 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
}
// 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
}

View File

@ -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++
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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,
}
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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}
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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())
}

View File

@ -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)
}

View File

@ -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,
}
}
}

View File

@ -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
}

View File

@ -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,

View File

@ -24,7 +24,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 (

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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,
}
}
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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))
}

View File

@ -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,
)
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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),
}),
},
{

View File

@ -30,7 +30,7 @@ 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
gotest.tools/v3 v3.5.1 // indirect
)
require (

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
// SimulateLegacyTextProposalContent returns a random text proposal content.
//
//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.
//
//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),

File diff suppressed because one or more lines are too long

View File

@ -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))
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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))
}

View File

@ -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

View File

@ -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
}

File diff suppressed because it is too large Load Diff

View File

@ -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))
}

Some files were not shown because too many files have changed in this diff Show More