Pre-requisite Readings
Synopsis
This document guides developers on integrating their custom modules with the Cosmos SDKSimulations
.
Simulations are useful for testing edge cases in module implementations.
- Simulation Package
- Simulation App Module
- SimsX
- Store decoders
- Randomized genesis
- Random weighted operations
- App Simulator manager
- Running Simulations
Simulation Package
The Cosmos SDK suggests organizing your simulation related code in ax/<module>/simulation
package.
Simulation App Module
To integrate with the Cosmos SDKSimulationManager
, app modules must implement the AppModuleSimulation
interface.
Copy
Ask AI
package module
import (
"encoding/json"
"math/rand"
"sort"
"time"
sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/types/simulation"
)
/ AppModuleSimulation defines the standard functions that every module should expose
/ for the SDK blockchain simulator
type AppModuleSimulation interface {
/ randomized genesis states
GenerateGenesisState(input *SimulationState)
/ register a func to decode the each module's defined types from their corresponding store key
RegisterStoreDecoder(simulation.StoreDecoderRegistry)
/ simulation operations (i.e msgs)
with their respective weight
WeightedOperations(simState SimulationState) []simulation.WeightedOperation
}
/ HasProposalMsgs defines the messages that can be used to simulate governance (v1)
proposals
type HasProposalMsgs interface {
/ msg functions used to simulate governance proposals
ProposalMsgs(simState SimulationState) []simulation.WeightedProposalMsg
}
/ 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
}
/ SimulationManager defines a simulation manager that provides the high level utility
/ for managing and executing simulation functionalities for a group of modules
type SimulationManager struct {
Modules []AppModuleSimulation / array of app modules; we use an array for deterministic simulation tests
StoreDecoders simulation.StoreDecoderRegistry / functions to decode the key-value pairs from each module's store
}
/ NewSimulationManager creates a new SimulationManager object
/
/ CONTRACT: All the modules provided must be also registered on the module Manager
func NewSimulationManager(modules ...AppModuleSimulation) *SimulationManager {
return &SimulationManager{
Modules: modules,
StoreDecoders: make(simulation.StoreDecoderRegistry),
}
}
/ NewSimulationManagerFromAppModules creates a new SimulationManager object.
/
/ First it sets any SimulationModule provided by overrideModules, and ignores any AppModule
/ with the same moduleName.
/ 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]any, overrideModules map[string]AppModuleSimulation) *SimulationManager {
simModules := []AppModuleSimulation{
}
appModuleNamesSorted := make([]string, 0, len(modules))
for moduleName := range modules {
appModuleNamesSorted = append(appModuleNamesSorted, moduleName)
}
sort.Strings(appModuleNamesSorted)
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.
/ otherwise no simulation module.
if simModule, ok := overrideModules[moduleName]; ok {
simModules = append(simModules, simModule)
}
else {
appModule := modules[moduleName]
if simModule, ok := appModule.(AppModuleSimulation); ok {
simModules = append(simModules, simModule)
}
/ cannot cast, so we continue
}
}
return NewSimulationManager(simModules...)
}
/ Deprecated: Use GetProposalMsgs instead.
/ GetProposalContents returns each module's proposal content generator function
/ with their default operation weight and key.
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 {
wContents = append(wContents, module.ProposalContents(simState)...)
}
}
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 {
module.RegisterStoreDecoder(sm.StoreDecoders)
}
}
/ GenerateGenesisStates generates a randomized GenesisState for each of the
/ registered modules
func (sm *SimulationManager)
GenerateGenesisStates(simState *SimulationState) {
for _, module := range sm.Modules {
module.GenerateGenesisState(simState)
}
}
/ 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 {
AppParams simulation.AppParams
Cdc codec.JSONCodec / application codec
TxConfig client.TxConfig / Shared TxConfig; this is expensive to create and stateless, so create it once up front.
Rand *rand.Rand / random number
GenState map[string]json.RawMessage / genesis state
Accounts []simulation.Account / simulation accounts
InitialStake sdkmath.Int / initial coins per account
NumBonded int64 / number of initially bonded accounts
BondDenom string / denom to be used as default
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
}
x/distribution
here.
SimsX
Cosmos SDK v0.53.0 introduced a new package,simsx
, providing improved DevX for writing simulation code.
It exposes the following extension interfaces that modules may implement to integrate with the new simsx
runner.
Copy
Ask AI
package simsx
import (
"encoding/json"
"fmt"
"io"
"math"
"os"
"path/filepath"
"strings"
"testing"
dbm "github.com/cosmos/cosmos-db"
"github.com/stretchr/testify/require"
"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"
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.AppI
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](/docs/sdk/next/documentation/operations/
t *testing.T,
appFactory func(
logger log.Logger,
db dbm.DB,
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], accs []simtypes.Account),
) {
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](/docs/sdk/next/documentation/operations/
t *testing.T,
appFactory func(
logger log.Logger,
db dbm.DB,
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], accs []simtypes.Account),
) {
t.Helper()
RunWithSeedsAndRandAcc(t, appFactory, setupStateFactory, seeds, fuzzSeed, simtypes.RandomAccounts, postRunActions...)
}
/ RunWithSeedsAndRandAcc calls RunWithSeeds with randAccFn
func RunWithSeedsAndRandAcc[T SimulationApp](/docs/sdk/next/documentation/operations/
t *testing.T,
appFactory func(
logger log.Logger,
db dbm.DB,
traceStore io.Writer,
loadLatest bool,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
)
T,
setupStateFactory func(app T)
SimStateFactory,
seeds []int64,
fuzzSeed []byte,
randAccFn simtypes.RandomAccountFn,
postRunActions ...func(t testing.TB, app TestInstance[T], accs []simtypes.Account),
) {
t.Helper()
if deprecatedParams := cli.GetDeprecatedFlagUsed(); len(deprecatedParams) != 0 {
fmt.Printf("Warning: Deprecated flag are used: %s", strings.Join(deprecatedParams, ","))
}
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](/docs/sdk/next/documentation/operations/
tb testing.TB,
cfg simtypes.Config,
appFactory func(logger log.Logger, db dbm.DB, 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], accs []simtypes.Account),
) {
tb.Helper()
RunWithSeedAndRandAcc(tb, cfg, appFactory, setupStateFactory, seed, fuzzSeed, simtypes.RandomAccounts, postRunActions...)
}
/ RunWithSeedAndRandAcc calls RunWithSeed with randAccFn
func RunWithSeedAndRandAcc[T SimulationApp](/docs/sdk/next/documentation/operations/
tb testing.TB,
cfg simtypes.Config,
appFactory func(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp))
T,
setupStateFactory func(app T)
SimStateFactory,
seed int64,
fuzzSeed []byte,
randAccFn simtypes.RandomAccountFn,
postRunActions ...func(t testing.TB, app TestInstance[T], accs []simtypes.Account),
) {
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, accs, err := simulation.SimulateFromSeedX(
tb,
runLogger,
WriteToDebugLog(runLogger),
app.GetBaseApp(),
stateFactory.AppStateFn,
randAccFn,
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 {
simtestutil.PrintStats(testInstance.DB)
}
/ 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, accs)
}
require.NoError(tb, app.Close())
}
type (
HasWeightedOperationsX interface {
WeightedOperationsX(weight WeightSource, reg Registry)
}
HasWeightedOperationsXWithProposals interface {
WeightedOperationsX(weights WeightSource, reg Registry, proposals WeightedProposalMsgIter,
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 dbm.DB
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
simState := module.SimulationState{
AppParams: make(simtypes.AppParams),
Cdc: cdc,
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)
wContent := make([]simtypes.WeightedProposalContent, 0) /nolint:staticcheck / required for legacy type
legacyPReg := NewWeightedFactoryMethods()
/ add gov proposals types
for _, m := range sm.Modules {
switch xm := m.(type) {
case HasProposalMsgsX:
xm.ProposalMsgsX(weights, pReg)
case HasLegacyProposalMsgs:
for _, p := range xm.ProposalMsgs(simState) {
weight := weights.Get(p.AppParamsKey(), safeUint(p.DefaultWeight()))
legacyPReg.Add(weight, legacyToMsgFactoryAdapter(p.MsgSimulatorFn()))
}
case HasLegacyProposalContents:
wContent = append(wContent, xm.ProposalContents(simState)...)
}
}
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, AppendIterators(legacyPReg.Iterator(), pReg.Iterator()), wContent)
case HasLegacyWeightedOperations:
wOps = append(wOps, xm.WeightedOperations(simState)...)
}
}
return append(wOps, Collect(oReg.items, func(a weightedOperation)
simtypes.WeightedOperation {
return a
})...), reporter
}
func safeUint(p int)
uint32 {
if p < 0 || p > math.MaxUint32 {
panic(fmt.Sprintf("can not cast to uint32: %d", p))
}
return uint32(p)
}
/ 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](/docs/sdk/next/documentation/operations/
tb testing.TB,
tCfg simtypes.Config,
appFactory func(logger log.Logger, db dbm.DB, 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"), 0o750))
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
opts := []func(*baseapp.BaseApp) {
baseapp.SetChainID(tCfg.ChainID)
}
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
})
}
/ FauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of
/ an IAVLStore for faster simulation speed.
func FauxMerkleModeOpt(bapp *baseapp.BaseApp) {
bapp.SetFauxMerkleMode()
}
Note that modules should not implement both
HasWeightedOperationsX
and HasWeightedOperationsXWithProposals
.
See the runner code here for detailsIf the module does not have message handlers or governance proposal handlers, these interface methods do not need to be implemented.Example Implementations
Store decoders
Registering the store decoders is required for theAppImportExport
simulation. This allows
for the key-value pairs from the stores to be decoded to their corresponding types.
In particular, it matches the key to a concrete type and then unmarshals the value from the KVPair
to the type provided.
Modules using collections can use the NewStoreDecoderFuncFromCollectionsSchema
function that builds the decoder for you:
Copy
Ask AI
package bank
import (
"context"
"encoding/json"
"fmt"
"maps"
"slices"
"sort"
gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/cobra"
modulev1 "cosmossdk.io/api/cosmos/bank/module/v1"
"cosmossdk.io/core/address"
"cosmossdk.io/core/appmodule"
corestore "cosmossdk.io/core/store"
"cosmossdk.io/depinject"
"cosmossdk.io/log"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/testutil/simsx"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/bank/client/cli"
"github.com/cosmos/cosmos-sdk/x/bank/exported"
"github.com/cosmos/cosmos-sdk/x/bank/keeper"
v1bank "github.com/cosmos/cosmos-sdk/x/bank/migrations/v1"
"github.com/cosmos/cosmos-sdk/x/bank/simulation"
"github.com/cosmos/cosmos-sdk/x/bank/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
)
/ ConsensusVersion defines the current x/bank module consensus version.
const ConsensusVersion = 4
var (
_ module.AppModuleBasic = AppModule{
}
_ module.AppModuleSimulation = AppModule{
}
_ module.HasGenesis = AppModule{
}
_ module.HasServices = AppModule{
}
_ appmodule.AppModule = AppModule{
}
)
/ AppModuleBasic defines the basic application module used by the bank module.
type AppModuleBasic struct {
cdc codec.Codec
ac address.Codec
}
/ Name returns the bank module's name.
func (AppModuleBasic)
Name()
string {
return types.ModuleName
}
/ RegisterLegacyAminoCodec registers the bank module's types on the LegacyAmino codec.
func (AppModuleBasic)
RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
types.RegisterLegacyAminoCodec(cdc)
}
/ DefaultGenesis returns default genesis state as raw bytes for the bank
/ module.
func (AppModuleBasic)
DefaultGenesis(cdc codec.JSONCodec)
json.RawMessage {
return cdc.MustMarshalJSON(types.DefaultGenesisState())
}
/ ValidateGenesis performs genesis state validation for the bank module.
func (AppModuleBasic)
ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage)
error {
var data types.GenesisState
if err := cdc.UnmarshalJSON(bz, &data); err != nil {
return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err)
}
return data.Validate()
}
/ RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the bank module.
func (AppModuleBasic)
RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *gwruntime.ServeMux) {
if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil {
panic(err)
}
}
/ GetTxCmd returns the root tx command for the bank module.
func (ab AppModuleBasic)
GetTxCmd() *cobra.Command {
return cli.NewTxCmd(ab.ac)
}
/ RegisterInterfaces registers interfaces and implementations of the bank module.
func (AppModuleBasic)
RegisterInterfaces(registry codectypes.InterfaceRegistry) {
types.RegisterInterfaces(registry)
/ Register legacy interfaces for migration scripts.
v1bank.RegisterInterfaces(registry)
}
/ AppModule implements an application module for the bank module.
type AppModule struct {
AppModuleBasic
keeper keeper.Keeper
accountKeeper types.AccountKeeper
/ legacySubspace is used solely for migration of x/params managed parameters
legacySubspace exported.Subspace
}
/ IsOnePerModuleType implements the depinject.OnePerModuleType interface.
func (am AppModule)
IsOnePerModuleType() {
}
/ IsAppModule implements the appmodule.AppModule interface.
func (am AppModule)
IsAppModule() {
}
/ RegisterServices registers module services.
func (am AppModule)
RegisterServices(cfg module.Configurator) {
types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper))
types.RegisterQueryServer(cfg.QueryServer(), am.keeper)
m := keeper.NewMigrator(am.keeper.(keeper.BaseKeeper), am.legacySubspace)
if err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2); err != nil {
panic(fmt.Sprintf("failed to migrate x/bank from version 1 to 2: %v", err))
}
if err := cfg.RegisterMigration(types.ModuleName, 2, m.Migrate2to3); err != nil {
panic(fmt.Sprintf("failed to migrate x/bank from version 2 to 3: %v", err))
}
if err := cfg.RegisterMigration(types.ModuleName, 3, m.Migrate3to4); err != nil {
panic(fmt.Sprintf("failed to migrate x/bank from version 3 to 4: %v", err))
}
}
/ NewAppModule creates a new AppModule object
func NewAppModule(cdc codec.Codec, keeper keeper.Keeper, accountKeeper types.AccountKeeper, ss exported.Subspace)
AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{
cdc: cdc, ac: accountKeeper.AddressCodec()
},
keeper: keeper,
accountKeeper: accountKeeper,
legacySubspace: ss,
}
}
/ QuerierRoute returns the bank module's querier route name.
func (AppModule)
QuerierRoute()
string {
return types.RouterKey
}
/ InitGenesis performs genesis initialization for the bank module. It returns
/ no validator updates.
func (am AppModule)
InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) {
var genesisState types.GenesisState
cdc.MustUnmarshalJSON(data, &genesisState)
am.keeper.InitGenesis(ctx, &genesisState)
}
/ ExportGenesis returns the exported genesis state as raw bytes for the bank
/ module.
func (am AppModule)
ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec)
json.RawMessage {
gs := am.keeper.ExportGenesis(ctx)
return cdc.MustMarshalJSON(gs)
}
/ ConsensusVersion implements AppModule/ConsensusVersion.
func (AppModule)
ConsensusVersion()
uint64 {
return ConsensusVersion
}
/ AppModuleSimulation functions
/ GenerateGenesisState creates a randomized GenState of the bank module.
func (AppModule)
GenerateGenesisState(simState *module.SimulationState) {
simulation.RandomizedGenState(simState)
}
/ ProposalMsgs returns msgs used for governance proposals for simulations.
/ migrate to ProposalMsgsX. This method is ignored when ProposalMsgsX exists and will be removed in the future.
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 bank module operations with their respective weights.
/ migrate to WeightedOperationsX. This method is ignored when WeightedOperationsX exists and will be removed in the future
func (am AppModule)
WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation {
return simulation.WeightedOperations(
simState.AppParams, simState.Cdc, simState.TxConfig, am.accountKeeper, am.keeper,
)
}
/ ProposalMsgsX registers governance proposal messages in the simulation registry.
func (AppModule)
ProposalMsgsX(weights simsx.WeightSource, reg simsx.Registry) {
reg.Add(weights.Get("msg_update_params", 100), simulation.MsgUpdateParamsFactory())
}
/ WeightedOperationsX registers weighted bank module operations for simulation.
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())
}
/ App Wiring Setup
func init() {
appmodule.Register(
&modulev1.Module{
},
appmodule.Provide(ProvideModule),
appmodule.Invoke(InvokeSetSendRestrictions),
)
}
type ModuleInputs struct {
depinject.In
Config *modulev1.Module
Cdc codec.Codec
StoreService corestore.KVStoreService
Logger log.Logger
AccountKeeper types.AccountKeeper
/ LegacySubspace is used solely for migration of x/params managed parameters
LegacySubspace exported.Subspace `optional:"true"`
}
type ModuleOutputs struct {
depinject.Out
BankKeeper keeper.BaseKeeper
Module appmodule.AppModule
}
func ProvideModule(in ModuleInputs)
ModuleOutputs {
/ Configure blocked module accounts.
/
/ Default behavior for blockedAddresses is to regard any module mentioned in
/ AccountKeeper's module account permissions as blocked.
blockedAddresses := make(map[string]bool)
if len(in.Config.BlockedModuleAccountsOverride) > 0 {
for _, moduleName := range in.Config.BlockedModuleAccountsOverride {
blockedAddresses[authtypes.NewModuleAddress(moduleName).String()] = true
}
}
else {
for _, permission := range in.AccountKeeper.GetModulePermissions() {
blockedAddresses[permission.GetAddress().String()] = true
}
}
/ default to governance authority if not provided
authority := authtypes.NewModuleAddress(govtypes.ModuleName)
if in.Config.Authority != "" {
authority = authtypes.NewModuleAddressOrBech32Address(in.Config.Authority)
}
bankKeeper := keeper.NewBaseKeeper(
in.Cdc,
in.StoreService,
in.AccountKeeper,
blockedAddresses,
authority.String(),
in.Logger,
)
m := NewAppModule(in.Cdc, bankKeeper, in.AccountKeeper, in.LegacySubspace)
return ModuleOutputs{
BankKeeper: bankKeeper,
Module: m
}
}
func InvokeSetSendRestrictions(
config *modulev1.Module,
keeper keeper.BaseKeeper,
restrictions map[string]types.SendRestrictionFn,
)
error {
if config == nil {
return nil
}
modules := slices.Collect(maps.Keys(restrictions))
order := config.RestrictionsOrder
if len(order) == 0 {
order = modules
sort.Strings(order)
}
if len(order) != len(modules) {
return fmt.Errorf("len(restrictions order: %v) != len(restriction modules: %v)", order, modules)
}
if len(modules) == 0 {
return nil
}
for _, module := range order {
restriction, ok := restrictions[module]
if !ok {
return fmt.Errorf("can't find send restriction for module %s", module)
}
keeper.AppendSendRestriction(restriction)
}
return nil
}
Randomized genesis
The simulator tests different scenarios and values for genesis parameters. App modules must implement aGenerateGenesisState
method to generate the initial random GenesisState
from a given seed.
Copy
Ask AI
package module
import (
"encoding/json"
"math/rand"
"sort"
"time"
sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/types/simulation"
)
/ AppModuleSimulation defines the standard functions that every module should expose
/ for the SDK blockchain simulator
type AppModuleSimulation interface {
/ randomized genesis states
GenerateGenesisState(input *SimulationState)
/ register a func to decode the each module's defined types from their corresponding store key
RegisterStoreDecoder(simulation.StoreDecoderRegistry)
/ simulation operations (i.e msgs)
with their respective weight
WeightedOperations(simState SimulationState) []simulation.WeightedOperation
}
/ HasProposalMsgs defines the messages that can be used to simulate governance (v1)
proposals
type HasProposalMsgs interface {
/ msg functions used to simulate governance proposals
ProposalMsgs(simState SimulationState) []simulation.WeightedProposalMsg
}
/ 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
}
/ SimulationManager defines a simulation manager that provides the high level utility
/ for managing and executing simulation functionalities for a group of modules
type SimulationManager struct {
Modules []AppModuleSimulation / array of app modules; we use an array for deterministic simulation tests
StoreDecoders simulation.StoreDecoderRegistry / functions to decode the key-value pairs from each module's store
}
/ NewSimulationManager creates a new SimulationManager object
/
/ CONTRACT: All the modules provided must be also registered on the module Manager
func NewSimulationManager(modules ...AppModuleSimulation) *SimulationManager {
return &SimulationManager{
Modules: modules,
StoreDecoders: make(simulation.StoreDecoderRegistry),
}
}
/ NewSimulationManagerFromAppModules creates a new SimulationManager object.
/
/ First it sets any SimulationModule provided by overrideModules, and ignores any AppModule
/ with the same moduleName.
/ 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]any, overrideModules map[string]AppModuleSimulation) *SimulationManager {
simModules := []AppModuleSimulation{
}
appModuleNamesSorted := make([]string, 0, len(modules))
for moduleName := range modules {
appModuleNamesSorted = append(appModuleNamesSorted, moduleName)
}
sort.Strings(appModuleNamesSorted)
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.
/ otherwise no simulation module.
if simModule, ok := overrideModules[moduleName]; ok {
simModules = append(simModules, simModule)
}
else {
appModule := modules[moduleName]
if simModule, ok := appModule.(AppModuleSimulation); ok {
simModules = append(simModules, simModule)
}
/ cannot cast, so we continue
}
}
return NewSimulationManager(simModules...)
}
/ Deprecated: Use GetProposalMsgs instead.
/ GetProposalContents returns each module's proposal content generator function
/ with their default operation weight and key.
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 {
wContents = append(wContents, module.ProposalContents(simState)...)
}
}
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 {
module.RegisterStoreDecoder(sm.StoreDecoders)
}
}
/ GenerateGenesisStates generates a randomized GenesisState for each of the
/ registered modules
func (sm *SimulationManager)
GenerateGenesisStates(simState *SimulationState) {
for _, module := range sm.Modules {
module.GenerateGenesisState(simState)
}
}
/ 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 {
AppParams simulation.AppParams
Cdc codec.JSONCodec / application codec
TxConfig client.TxConfig / Shared TxConfig; this is expensive to create and stateless, so create it once up front.
Rand *rand.Rand / random number
GenState map[string]json.RawMessage / genesis state
Accounts []simulation.Account / simulation accounts
InitialStake sdkmath.Int / initial coins per account
NumBonded int64 / number of initially bonded accounts
BondDenom string / denom to be used as default
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
}
x/auth
here.
Once the module’s genesis parameters are generated randomly (or with the key and
values defined in a params
file), they are marshaled to JSON format and added
to the app genesis JSON for the simulation.
Random weighted operations
Operations are one of the crucial parts of the Cosmos SDK simulation. They are the transactions (Msg
) that are simulated with random field values. The sender of the operation
is also assigned randomly.
Operations on the simulation are simulated using the full transaction cycle of a
ABCI
application that exposes the BaseApp
.
Using Simsx
Simsx introduces the ability to define aMsgFactory
for each of a module’s messages.
These factories are registered in WeightedOperationsX
and/or ProposalMsgsX
.
Copy
Ask AI
package distribution
import (
"context"
"encoding/json"
"fmt"
gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/cobra"
modulev1 "cosmossdk.io/api/cosmos/distribution/module/v1"
"cosmossdk.io/core/address"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/store"
"cosmossdk.io/depinject"
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/testutil/simsx"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/distribution/client/cli"
"github.com/cosmos/cosmos-sdk/x/distribution/exported"
"github.com/cosmos/cosmos-sdk/x/distribution/keeper"
"github.com/cosmos/cosmos-sdk/x/distribution/simulation"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
staking "github.com/cosmos/cosmos-sdk/x/staking/types"
)
/ ConsensusVersion defines the current x/distribution module consensus version.
const ConsensusVersion = 3
var (
_ module.AppModuleBasic = AppModule{
}
_ module.AppModuleSimulation = AppModule{
}
_ module.HasGenesis = AppModule{
}
_ module.HasServices = AppModule{
}
_ appmodule.AppModule = AppModule{
}
_ appmodule.HasBeginBlocker = AppModule{
}
)
/ AppModuleBasic defines the basic application module used by the distribution module.
type AppModuleBasic struct {
cdc codec.Codec
ac address.Codec
}
/ Name returns the distribution module's name.
func (AppModuleBasic)
Name()
string {
return types.ModuleName
}
/ RegisterLegacyAminoCodec registers the distribution module's types for the given codec.
func (AppModuleBasic)
RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
types.RegisterLegacyAminoCodec(cdc)
}
/ DefaultGenesis returns default genesis state as raw bytes for the distribution
/ module.
func (AppModuleBasic)
DefaultGenesis(cdc codec.JSONCodec)
json.RawMessage {
return cdc.MustMarshalJSON(types.DefaultGenesisState())
}
/ ValidateGenesis performs genesis state validation for the distribution module.
func (AppModuleBasic)
ValidateGenesis(cdc codec.JSONCodec, _ sdkclient.TxEncodingConfig, bz json.RawMessage)
error {
var data types.GenesisState
if err := cdc.UnmarshalJSON(bz, &data); err != nil {
return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err)
}
return types.ValidateGenesis(&data)
}
/ RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the distribution module.
func (AppModuleBasic)
RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *gwruntime.ServeMux) {
if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil {
panic(err)
}
}
/ GetTxCmd returns the root tx command for the distribution module.
func (ab AppModuleBasic)
GetTxCmd() *cobra.Command {
return cli.NewTxCmd(ab.cdc.InterfaceRegistry().SigningContext().ValidatorAddressCodec(), ab.cdc.InterfaceRegistry().SigningContext().AddressCodec())
}
/ RegisterInterfaces implements InterfaceModule
func (AppModuleBasic)
RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
types.RegisterInterfaces(registry)
}
/ AppModule implements an application module for the distribution module.
type AppModule struct {
AppModuleBasic
keeper keeper.Keeper
accountKeeper types.AccountKeeper
bankKeeper types.BankKeeper
stakingKeeper types.StakingKeeper
/ legacySubspace is used solely for migration of x/params managed parameters
legacySubspace exported.Subspace
}
/ NewAppModule creates a new AppModule object
func NewAppModule(
cdc codec.Codec, keeper keeper.Keeper, accountKeeper types.AccountKeeper,
bankKeeper types.BankKeeper, stakingKeeper types.StakingKeeper, ss exported.Subspace,
)
AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{
cdc: cdc, ac: accountKeeper.AddressCodec()
},
keeper: keeper,
accountKeeper: accountKeeper,
bankKeeper: bankKeeper,
stakingKeeper: stakingKeeper,
legacySubspace: ss,
}
}
/ IsOnePerModuleType implements the depinject.OnePerModuleType interface.
func (am AppModule)
IsOnePerModuleType() {
}
/ IsAppModule implements the appmodule.AppModule interface.
func (am AppModule)
IsAppModule() {
}
/ RegisterServices registers module services.
func (am AppModule)
RegisterServices(cfg module.Configurator) {
types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper))
types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQuerier(am.keeper))
m := keeper.NewMigrator(am.keeper, am.legacySubspace)
if err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2); err != nil {
panic(fmt.Sprintf("failed to migrate x/%s from version 1 to 2: %v", types.ModuleName, err))
}
if err := cfg.RegisterMigration(types.ModuleName, 2, m.Migrate2to3); err != nil {
panic(fmt.Sprintf("failed to migrate x/%s from version 2 to 3: %v", types.ModuleName, err))
}
}
/ InitGenesis performs genesis initialization for the distribution module. It returns
/ no validator updates.
func (am AppModule)
InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) {
var genesisState types.GenesisState
cdc.MustUnmarshalJSON(data, &genesisState)
am.keeper.InitGenesis(ctx, genesisState)
}
/ ExportGenesis returns the exported genesis state as raw bytes for the distribution
/ module.
func (am AppModule)
ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec)
json.RawMessage {
gs := am.keeper.ExportGenesis(ctx)
return cdc.MustMarshalJSON(gs)
}
/ ConsensusVersion implements AppModule/ConsensusVersion.
func (AppModule)
ConsensusVersion()
uint64 {
return ConsensusVersion
}
/ BeginBlock returns the begin blocker for the distribution module.
func (am AppModule)
BeginBlock(ctx context.Context)
error {
c := sdk.UnwrapSDKContext(ctx)
return BeginBlocker(c, am.keeper)
}
/ AppModuleSimulation functions
/ GenerateGenesisState creates a randomized GenState of the distribution module.
func (AppModule)
GenerateGenesisState(simState *module.SimulationState) {
simulation.RandomizedGenState(simState)
}
/ ProposalMsgs returns msgs used for governance proposals for simulations.
/ migrate to ProposalMsgsX. This method is ignored when ProposalMsgsX exists and will be removed in the future.
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.
/ migrate to WeightedOperationsX. This method is ignored when WeightedOperationsX exists and will be removed in the future
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 registers governance proposal messages in the simulation registry.
func (AppModule)
ProposalMsgsX(weights simsx.WeightSource, reg simsx.Registry) {
reg.Add(weights.Get("msg_update_params", 100), simulation.MsgUpdateParamsFactory())
}
/ WeightedOperationsX registers weighted distribution module operations for simulation.
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))
}
/
/ App Wiring Setup
/
func init() {
appmodule.Register(&modulev1.Module{
},
appmodule.Provide(ProvideModule),
)
}
type ModuleInputs struct {
depinject.In
Config *modulev1.Module
StoreService store.KVStoreService
Cdc codec.Codec
AccountKeeper types.AccountKeeper
BankKeeper types.BankKeeper
StakingKeeper types.StakingKeeper
ExternalPoolKeeper types.ExternalCommunityPoolKeeper `optional:"true"`
/ LegacySubspace is used solely for migration of x/params managed parameters
LegacySubspace exported.Subspace `optional:"true"`
}
type ModuleOutputs struct {
depinject.Out
DistrKeeper keeper.Keeper
Module appmodule.AppModule
Hooks staking.StakingHooksWrapper
}
func ProvideModule(in ModuleInputs)
ModuleOutputs {
feeCollectorName := in.Config.FeeCollectorName
if feeCollectorName == "" {
feeCollectorName = authtypes.FeeCollectorName
}
/ default to governance authority if not provided
authority := authtypes.NewModuleAddress(govtypes.ModuleName)
if in.Config.Authority != "" {
authority = authtypes.NewModuleAddressOrBech32Address(in.Config.Authority)
}
var opts []keeper.InitOption
if in.ExternalPoolKeeper != nil {
opts = append(opts, keeper.WithExternalCommunityPool(in.ExternalPoolKeeper))
}
k := keeper.NewKeeper(
in.Cdc,
in.StoreService,
in.AccountKeeper,
in.BankKeeper,
in.StakingKeeper,
feeCollectorName,
authority.String(),
opts...,
)
m := NewAppModule(in.Cdc, k, in.AccountKeeper, in.BankKeeper, in.StakingKeeper, in.LegacySubspace)
return ModuleOutputs{
DistrKeeper: k,
Module: m,
Hooks: staking.StakingHooksWrapper{
StakingHooks: k.Hooks()
},
}
}
weights.Get
must match the name of the operation set in the WeightedOperations
.
For example, if the module contains an operation op_weight_msg_set_withdraw_address
, the name passed to weights.Get
should be msg_set_withdraw_address
.
See the x/distribution
for an example of implementing message factories here
App Simulator manager
The following step is setting up theSimulatorManager
at the app level. This
is required for the simulation test files in the next step.
Copy
Ask AI
type CoolApp struct {
...
sm *module.SimulationManager
}
ModuleManager
and call the RegisterStoreDecoders
method.
Copy
Ask AI
/go:build app_v1
package simapp
import (
"encoding/json"
"fmt"
"io"
"maps"
abci "github.com/cometbft/cometbft/abci/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/cosmos/gogoproto/proto"
"github.com/spf13/cast"
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1"
"cosmossdk.io/client/v2/autocli"
clienthelpers "cosmossdk.io/client/v2/helpers"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/tx/signing"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/grpc/cmtservice"
nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/address"
"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/runtime"
runtimeservices "github.com/cosmos/cosmos-sdk/runtime/services"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/api"
"github.com/cosmos/cosmos-sdk/server/config"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/std"
testdata_pulsar "github.com/cosmos/cosmos-sdk/testutil/testdata/testpb"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
sigtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
"github.com/cosmos/cosmos-sdk/x/auth/posthandler"
authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation"
"github.com/cosmos/cosmos-sdk/x/auth/tx"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
"github.com/cosmos/cosmos-sdk/x/authz"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module"
"github.com/cosmos/cosmos-sdk/x/bank"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
consensus "github.com/cosmos/cosmos-sdk/x/consensus"
consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper"
consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/cosmos/cosmos-sdk/x/epochs"
epochskeeper "github.com/cosmos/cosmos-sdk/x/epochs/keeper"
epochstypes "github.com/cosmos/cosmos-sdk/x/epochs/types"
"github.com/cosmos/cosmos-sdk/x/evidence"
evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper"
evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
"github.com/cosmos/cosmos-sdk/x/feegrant"
feegrantkeeper "github.com/cosmos/cosmos-sdk/x/feegrant/keeper"
feegrantmodule "github.com/cosmos/cosmos-sdk/x/feegrant/module"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
"github.com/cosmos/cosmos-sdk/x/gov"
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
"github.com/cosmos/cosmos-sdk/x/mint"
mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
"github.com/cosmos/cosmos-sdk/x/protocolpool"
protocolpoolkeeper "github.com/cosmos/cosmos-sdk/x/protocolpool/keeper"
protocolpooltypes "github.com/cosmos/cosmos-sdk/x/protocolpool/types"
"github.com/cosmos/cosmos-sdk/x/slashing"
slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/cosmos/cosmos-sdk/x/upgrade"
upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
)
const appName = "SimApp"
var (
/ DefaultNodeHome default home directories for the application daemon
DefaultNodeHome string
/ module account permissions
maccPerms = map[string][]string{
authtypes.FeeCollectorName: nil,
distrtypes.ModuleName: nil,
minttypes.ModuleName: {
authtypes.Minter
},
stakingtypes.BondedPoolName: {
authtypes.Burner, authtypes.Staking
},
stakingtypes.NotBondedPoolName: {
authtypes.Burner, authtypes.Staking
},
govtypes.ModuleName: {
authtypes.Burner
},
protocolpooltypes.ModuleName: nil,
protocolpooltypes.ProtocolPoolEscrowAccount: nil
}
)
var (
_ runtime.AppI = (*SimApp)(nil)
_ servertypes.Application = (*SimApp)(nil)
)
/ SimApp extends an ABCI application, but with most of its parameters exported.
/ They are exported for convenience in creating helper functions, as object
/ capabilities aren't needed for testing.
type SimApp struct {
*baseapp.BaseApp
legacyAmino *codec.LegacyAmino
appCodec codec.Codec
txConfig client.TxConfig
interfaceRegistry types.InterfaceRegistry
/ keys to access the substores
keys map[string]*storetypes.KVStoreKey
/ essential keepers
AccountKeeper authkeeper.AccountKeeper
BankKeeper bankkeeper.BaseKeeper
StakingKeeper *stakingkeeper.Keeper
SlashingKeeper slashingkeeper.Keeper
MintKeeper mintkeeper.Keeper
DistrKeeper distrkeeper.Keeper
GovKeeper govkeeper.Keeper
UpgradeKeeper *upgradekeeper.Keeper
EvidenceKeeper evidencekeeper.Keeper
ConsensusParamsKeeper consensusparamkeeper.Keeper
/ supplementary keepers
FeeGrantKeeper feegrantkeeper.Keeper
AuthzKeeper authzkeeper.Keeper
EpochsKeeper epochskeeper.Keeper
ProtocolPoolKeeper protocolpoolkeeper.Keeper
/ the module manager
ModuleManager *module.Manager
BasicModuleManager module.BasicManager
/ simulation manager
sm *module.SimulationManager
/ module configurator
configurator module.Configurator
}
func init() {
var err error
DefaultNodeHome, err = clienthelpers.GetNodeHomeDirectory(".simapp")
if err != nil {
panic(err)
}
}
/ NewSimApp returns a reference to an initialized SimApp.
func NewSimApp(
logger log.Logger,
db dbm.DB,
traceStore io.Writer,
loadLatest bool,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
) *SimApp {
interfaceRegistry, _ := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{
ProtoFiles: proto.HybridResolver,
SigningOptions: signing.Options{
AddressCodec: address.Bech32Codec{
Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(),
},
ValidatorAddressCodec: address.Bech32Codec{
Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(),
},
},
})
appCodec := codec.NewProtoCodec(interfaceRegistry)
legacyAmino := codec.NewLegacyAmino()
txConfig := tx.NewTxConfig(appCodec, tx.DefaultSignModes)
if err := interfaceRegistry.SigningContext().Validate(); err != nil {
panic(err)
}
std.RegisterLegacyAminoCodec(legacyAmino)
std.RegisterInterfaces(interfaceRegistry)
/ Below we could construct and set an application specific mempool and
/ ABCI 1.0 PrepareProposal and ProcessProposal handlers. These defaults are
/ already set in the SDK's BaseApp, this shows an example of how to override
/ them.
/
/ Example:
/
/ bApp := baseapp.NewBaseApp(...)
/ nonceMempool := mempool.NewSenderNonceMempool()
/ abciPropHandler := NewDefaultProposalHandler(nonceMempool, bApp)
/
/ bApp.SetMempool(nonceMempool)
/ bApp.SetPrepareProposal(abciPropHandler.PrepareProposalHandler())
/ bApp.SetProcessProposal(abciPropHandler.ProcessProposalHandler())
/
/ Alternatively, you can construct BaseApp options, append those to
/ baseAppOptions and pass them to NewBaseApp.
/
/ Example:
/
/ prepareOpt = func(app *baseapp.BaseApp) {
/ abciPropHandler := baseapp.NewDefaultProposalHandler(nonceMempool, app)
/ app.SetPrepareProposal(abciPropHandler.PrepareProposalHandler())
/
}
/ baseAppOptions = append(baseAppOptions, prepareOpt)
/ create and set dummy vote extension handler
voteExtOp := func(bApp *baseapp.BaseApp) {
voteExtHandler := NewVoteExtensionHandler()
voteExtHandler.SetHandlers(bApp)
}
baseAppOptions = append(baseAppOptions, voteExtOp, baseapp.SetOptimisticExecution())
bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...)
bApp.SetCommitMultiStoreTracer(traceStore)
bApp.SetVersion(version.Version)
bApp.SetInterfaceRegistry(interfaceRegistry)
bApp.SetTxEncoder(txConfig.TxEncoder())
keys := storetypes.NewKVStoreKeys(
authtypes.StoreKey,
banktypes.StoreKey,
stakingtypes.StoreKey,
minttypes.StoreKey,
distrtypes.StoreKey,
slashingtypes.StoreKey,
govtypes.StoreKey,
consensusparamtypes.StoreKey,
upgradetypes.StoreKey,
feegrant.StoreKey,
evidencetypes.StoreKey,
authzkeeper.StoreKey,
epochstypes.StoreKey,
protocolpooltypes.StoreKey,
)
/ register streaming services
if err := bApp.RegisterStreamingServices(appOpts, keys); err != nil {
panic(err)
}
app := &SimApp{
BaseApp: bApp,
legacyAmino: legacyAmino,
appCodec: appCodec,
txConfig: txConfig,
interfaceRegistry: interfaceRegistry,
keys: keys,
}
/ set the BaseApp's parameter store
app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]),
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
runtime.EventService{
},
)
bApp.SetParamStore(app.ConsensusParamsKeeper.ParamsStore)
/ add keepers
app.AccountKeeper = authkeeper.NewAccountKeeper(
appCodec,
runtime.NewKVStoreService(keys[authtypes.StoreKey]),
authtypes.ProtoBaseAccount,
maccPerms,
authcodec.NewBech32Codec(sdk.Bech32MainPrefix),
sdk.Bech32MainPrefix,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
authkeeper.WithUnorderedTransactions(true),
)
app.BankKeeper = bankkeeper.NewBaseKeeper(
appCodec,
runtime.NewKVStoreService(keys[banktypes.StoreKey]),
app.AccountKeeper,
BlockedAddresses(),
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
logger,
)
/ optional: enable sign mode textual by overwriting the default tx config (after setting the bank keeper)
enabledSignModes := append(tx.DefaultSignModes, sigtypes.SignMode_SIGN_MODE_TEXTUAL)
txConfigOpts := tx.ConfigOptions{
EnabledSignModes: enabledSignModes,
TextualCoinMetadataQueryFn: txmodule.NewBankKeeperCoinMetadataQueryFn(app.BankKeeper),
}
txConfig, err := tx.NewTxConfigWithOptions(
appCodec,
txConfigOpts,
)
if err != nil {
panic(err)
}
app.txConfig = txConfig
app.StakingKeeper = stakingkeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[stakingtypes.StoreKey]),
app.AccountKeeper,
app.BankKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
authcodec.NewBech32Codec(sdk.Bech32PrefixValAddr),
authcodec.NewBech32Codec(sdk.Bech32PrefixConsAddr),
)
app.MintKeeper = mintkeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[minttypes.StoreKey]),
app.StakingKeeper,
app.AccountKeeper,
app.BankKeeper,
authtypes.FeeCollectorName,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
/ mintkeeper.WithMintFn(mintkeeper.DefaultMintFn(minttypes.DefaultInflationCalculationFn)), custom mintFn can be added here
)
app.ProtocolPoolKeeper = protocolpoolkeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[protocolpooltypes.StoreKey]),
app.AccountKeeper,
app.BankKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
app.DistrKeeper = distrkeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[distrtypes.StoreKey]),
app.AccountKeeper,
app.BankKeeper,
app.StakingKeeper,
authtypes.FeeCollectorName,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
distrkeeper.WithExternalCommunityPool(app.ProtocolPoolKeeper),
)
app.SlashingKeeper = slashingkeeper.NewKeeper(
appCodec,
legacyAmino,
runtime.NewKVStoreService(keys[slashingtypes.StoreKey]),
app.StakingKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
app.FeeGrantKeeper = feegrantkeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[feegrant.StoreKey]),
app.AccountKeeper,
)
/ register the staking hooks
/ NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
app.StakingKeeper.SetHooks(
stakingtypes.NewMultiStakingHooks(
app.DistrKeeper.Hooks(),
app.SlashingKeeper.Hooks(),
),
)
app.AuthzKeeper = authzkeeper.NewKeeper(
runtime.NewKVStoreService(keys[authzkeeper.StoreKey]),
appCodec,
app.MsgServiceRouter(),
app.AccountKeeper,
)
/ get skipUpgradeHeights from the app options
skipUpgradeHeights := map[int64]bool{
}
for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) {
skipUpgradeHeights[int64(h)] = true
}
homePath := cast.ToString(appOpts.Get(flags.FlagHome))
/ set the governance module account as the authority for conducting upgrades
app.UpgradeKeeper = upgradekeeper.NewKeeper(
skipUpgradeHeights,
runtime.NewKVStoreService(keys[upgradetypes.StoreKey]),
appCodec,
homePath,
app.BaseApp,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
/ Register the proposal types
/ Deprecated: Avoid adding new handlers, instead use the new proposal flow
/ by granting the governance module the right to execute the message.
/ See: https://docs.cosmos.network/main/modules/gov#proposal-messages
govRouter := govv1beta1.NewRouter()
govRouter.AddRoute(govtypes.RouterKey, govv1beta1.ProposalHandler)
govConfig := govtypes.DefaultConfig()
/*
Example of setting gov params:
govConfig.MaxMetadataLen = 10000
*/
govKeeper := govkeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[govtypes.StoreKey]),
app.AccountKeeper,
app.BankKeeper,
app.StakingKeeper,
app.DistrKeeper,
app.MsgServiceRouter(),
govConfig,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
/ govkeeper.WithCustomCalculateVoteResultsAndVotingPowerFn(...), / Add if you want to use a custom vote calculation function.
)
/ Set legacy router for backwards compatibility with gov v1beta1
govKeeper.SetLegacyRouter(govRouter)
app.GovKeeper = *govKeeper.SetHooks(
govtypes.NewMultiGovHooks(
/ register the governance hooks
),
)
/ create evidence keeper with router
evidenceKeeper := evidencekeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[evidencetypes.StoreKey]),
app.StakingKeeper,
app.SlashingKeeper,
app.AccountKeeper.AddressCodec(),
runtime.ProvideCometInfoService(),
)
/ If evidence needs to be handled for the app, set routes in router here and seal
app.EvidenceKeeper = *evidenceKeeper
app.EpochsKeeper = epochskeeper.NewKeeper(
runtime.NewKVStoreService(keys[epochstypes.StoreKey]),
appCodec,
)
app.EpochsKeeper.SetHooks(
epochstypes.NewMultiEpochHooks(
/ insert epoch hooks receivers here
),
)
/**** Module Options ****/
/ NOTE: Any module instantiated in the module manager that is later modified
/ must be passed by reference here.
app.ModuleManager = module.NewManager(
genutil.NewAppModule(
app.AccountKeeper, app.StakingKeeper, app,
txConfig,
),
auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, nil),
vesting.NewAppModule(app.AccountKeeper, app.BankKeeper),
bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, nil),
feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry),
gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, nil),
mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, nil),
slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, nil, app.interfaceRegistry),
distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, nil),
staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, nil),
upgrade.NewAppModule(app.UpgradeKeeper, app.AccountKeeper.AddressCodec()),
evidence.NewAppModule(app.EvidenceKeeper),
authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry),
consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper),
epochs.NewAppModule(app.EpochsKeeper),
protocolpool.NewAppModule(app.ProtocolPoolKeeper, app.AccountKeeper, app.BankKeeper),
)
/ BasicModuleManager defines the module BasicManager is in charge of setting up basic,
/ non-dependent module elements, such as codec registration and genesis verification.
/ By default it is composed of all the module from the module manager.
/ Additionally, app module basics can be overwritten by passing them as argument.
app.BasicModuleManager = module.NewBasicManagerFromManager(
app.ModuleManager,
map[string]module.AppModuleBasic{
genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator),
govtypes.ModuleName: gov.NewAppModuleBasic(
[]govclient.ProposalHandler{
},
),
})
app.BasicModuleManager.RegisterLegacyAminoCodec(legacyAmino)
app.BasicModuleManager.RegisterInterfaces(interfaceRegistry)
/ NOTE: upgrade module is required to be prioritized
app.ModuleManager.SetOrderPreBlockers(
upgradetypes.ModuleName,
authtypes.ModuleName,
)
/ During begin block slashing happens after distr.BeginBlocker so that
/ there is nothing left over in the validator fee pool, so as to keep the
/ CanWithdrawInvariant invariant.
/ NOTE: staking module is required if HistoricalEntries param > 0
app.ModuleManager.SetOrderBeginBlockers(
minttypes.ModuleName,
distrtypes.ModuleName,
protocolpooltypes.ModuleName,
slashingtypes.ModuleName,
evidencetypes.ModuleName,
stakingtypes.ModuleName,
genutiltypes.ModuleName,
authz.ModuleName,
epochstypes.ModuleName,
)
app.ModuleManager.SetOrderEndBlockers(
govtypes.ModuleName,
stakingtypes.ModuleName,
genutiltypes.ModuleName,
feegrant.ModuleName,
protocolpooltypes.ModuleName,
)
/ NOTE: The genutils module must occur after staking so that pools are
/ properly initialized with tokens from genesis accounts.
/ NOTE: The genutils module must also occur after auth so that it can access the params from auth.
genesisModuleOrder := []string{
authtypes.ModuleName,
banktypes.ModuleName,
distrtypes.ModuleName,
stakingtypes.ModuleName,
slashingtypes.ModuleName,
govtypes.ModuleName,
minttypes.ModuleName,
genutiltypes.ModuleName,
evidencetypes.ModuleName,
authz.ModuleName,
feegrant.ModuleName,
upgradetypes.ModuleName,
vestingtypes.ModuleName,
consensusparamtypes.ModuleName,
epochstypes.ModuleName,
protocolpooltypes.ModuleName,
}
exportModuleOrder := []string{
consensusparamtypes.ModuleName,
authtypes.ModuleName,
protocolpooltypes.ModuleName, / Must be exported before bank
banktypes.ModuleName,
distrtypes.ModuleName,
stakingtypes.ModuleName,
slashingtypes.ModuleName,
govtypes.ModuleName,
minttypes.ModuleName,
genutiltypes.ModuleName,
evidencetypes.ModuleName,
authz.ModuleName,
feegrant.ModuleName,
upgradetypes.ModuleName,
vestingtypes.ModuleName,
epochstypes.ModuleName,
}
app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...)
app.ModuleManager.SetOrderExportGenesis(exportModuleOrder...)
/ Uncomment if you want to set a custom migration order here.
/ app.ModuleManager.SetOrderMigrations(custom order)
app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
err = app.ModuleManager.RegisterServices(app.configurator)
if err != nil {
panic(err)
}
/ RegisterUpgradeHandlers is used for registering any on-chain upgrades.
/ Make sure it's called after `app.ModuleManager` and `app.configurator` are set.
app.RegisterUpgradeHandlers()
autocliv1.RegisterQueryServer(app.GRPCQueryRouter(), runtimeservices.NewAutoCLIQueryService(app.ModuleManager.Modules))
reflectionSvc, err := runtimeservices.NewReflectionService()
if err != nil {
panic(err)
}
reflectionv1.RegisterReflectionServiceServer(app.GRPCQueryRouter(), reflectionSvc)
/ add test gRPC service for testing gRPC queries in isolation
testdata_pulsar.RegisterQueryServer(app.GRPCQueryRouter(), testdata_pulsar.QueryImpl{
})
/ create the simulation manager and define the order of the modules for deterministic simulations
/
/ NOTE: this is not required apps that don't use the simulator for fuzz testing
/ transactions
overrideModules := map[string]module.AppModuleSimulation{
authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, nil),
}
app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules)
app.sm.RegisterStoreDecoders()
/ initialize stores
app.MountKVStores(keys)
/ initialize BaseApp
app.SetInitChainer(app.InitChainer)
app.SetPreBlocker(app.PreBlocker)
app.SetBeginBlocker(app.BeginBlocker)
app.SetEndBlocker(app.EndBlocker)
app.setAnteHandler(txConfig)
/ In v0.46, the SDK introduces _postHandlers_. PostHandlers are like
/ antehandlers, but are run _after_ the `runMsgs` execution. They are also
/ defined as a chain, and have the same signature as antehandlers.
/
/ In baseapp, postHandlers are run in the same store branch as `runMsgs`,
/ meaning that both `runMsgs` and `postHandler` state will be committed if
/ both are successful, and both will be reverted if any of the two fails.
/
/ The SDK exposes a default postHandlers chain
/
/ Please note that changing any of the anteHandler or postHandler chain is
/ likely to be a state-machine breaking change, which needs a coordinated
/ upgrade.
app.setPostHandler()
if loadLatest {
if err := app.LoadLatestVersion(); err != nil {
panic(fmt.Errorf("error loading last version: %w", err))
}
}
return app
}
func (app *SimApp)
setAnteHandler(txConfig client.TxConfig) {
anteHandler, err := ante.NewAnteHandler(
ante.HandlerOptions{
AccountKeeper: app.AccountKeeper,
BankKeeper: app.BankKeeper,
SignModeHandler: txConfig.SignModeHandler(),
FeegrantKeeper: app.FeeGrantKeeper,
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
SigVerifyOptions: []ante.SigVerificationDecoratorOption{
/ change below as needed.
ante.WithUnorderedTxGasCost(ante.DefaultUnorderedTxGasCost),
ante.WithMaxUnorderedTxTimeoutDuration(ante.DefaultMaxTimeoutDuration),
},
},
)
if err != nil {
panic(err)
}
/ Set the AnteHandler for the app
app.SetAnteHandler(anteHandler)
}
func (app *SimApp)
setPostHandler() {
postHandler, err := posthandler.NewPostHandler(
posthandler.HandlerOptions{
},
)
if err != nil {
panic(err)
}
app.SetPostHandler(postHandler)
}
/ Name returns the name of the App
func (app *SimApp)
Name()
string {
return app.BaseApp.Name()
}
/ PreBlocker application updates every pre block
func (app *SimApp)
PreBlocker(ctx sdk.Context, _ *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) {
return app.ModuleManager.PreBlock(ctx)
}
/ BeginBlocker application updates every begin block
func (app *SimApp)
BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) {
return app.ModuleManager.BeginBlock(ctx)
}
/ EndBlocker application updates every end block
func (app *SimApp)
EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) {
return app.ModuleManager.EndBlock(ctx)
}
func (a *SimApp)
Configurator()
module.Configurator {
return a.configurator
}
/ InitChainer application update at chain initialization
func (app *SimApp)
InitChainer(ctx sdk.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) {
var genesisState GenesisState
if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil {
panic(err)
}
app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap())
return app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState)
}
/ LoadHeight loads a particular height
func (app *SimApp)
LoadHeight(height int64)
error {
return app.LoadVersion(height)
}
/ LegacyAmino returns SimApp's amino codec.
/
/ NOTE: This is solely to be used for testing purposes as it may be desirable
/ for modules to register their own custom testing types.
func (app *SimApp)
LegacyAmino() *codec.LegacyAmino {
return app.legacyAmino
}
/ AppCodec returns SimApp's app codec.
/
/ NOTE: This is solely to be used for testing purposes as it may be desirable
/ for modules to register their own custom testing types.
func (app *SimApp)
AppCodec()
codec.Codec {
return app.appCodec
}
/ InterfaceRegistry returns SimApp's InterfaceRegistry
func (app *SimApp)
InterfaceRegistry()
types.InterfaceRegistry {
return app.interfaceRegistry
}
/ TxConfig returns SimApp's TxConfig
func (app *SimApp)
TxConfig()
client.TxConfig {
return app.txConfig
}
/ AutoCliOpts returns the autocli options for the app.
func (app *SimApp)
AutoCliOpts()
autocli.AppOptions {
modules := make(map[string]appmodule.AppModule, 0)
for _, m := range app.ModuleManager.Modules {
if moduleWithName, ok := m.(module.HasName); ok {
moduleName := moduleWithName.Name()
if appModule, ok := moduleWithName.(appmodule.AppModule); ok {
modules[moduleName] = appModule
}
}
}
return autocli.AppOptions{
Modules: modules,
ModuleOptions: runtimeservices.ExtractAutoCLIOptions(app.ModuleManager.Modules),
AddressCodec: authcodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()),
ValidatorAddressCodec: authcodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()),
ConsensusAddressCodec: authcodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()),
}
}
/ DefaultGenesis returns a default genesis from the registered AppModuleBasic's.
func (a *SimApp)
DefaultGenesis()
map[string]json.RawMessage {
return a.BasicModuleManager.DefaultGenesis(a.appCodec)
}
/ GetKey returns the KVStoreKey for the provided store key.
/
/ NOTE: This is solely to be used for testing purposes.
func (app *SimApp)
GetKey(storeKey string) *storetypes.KVStoreKey {
return app.keys[storeKey]
}
/ GetStoreKeys returns all the stored store keys.
func (app *SimApp)
GetStoreKeys() []storetypes.StoreKey {
keys := make([]storetypes.StoreKey, 0, len(app.keys))
for _, key := range app.keys {
keys = append(keys, key)
}
return keys
}
/ SimulationManager implements the SimulationApp interface
func (app *SimApp)
SimulationManager() *module.SimulationManager {
return app.sm
}
/ RegisterAPIRoutes registers all application module routes with the provided
/ API server.
func (app *SimApp)
RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) {
clientCtx := apiSvr.ClientCtx
/ Register new tx routes from grpc-gateway.
authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
/ Register new CometBFT queries routes from grpc-gateway.
cmtservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
/ Register node gRPC service for grpc-gateway.
nodeservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
/ Register grpc-gateway routes for all modules.
app.BasicModuleManager.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
/ register swagger API from root so that other applications can override easily
if err := server.RegisterSwaggerAPI(apiSvr.ClientCtx, apiSvr.Router, apiConfig.Swagger); err != nil {
panic(err)
}
}
/ RegisterTxService implements the Application.RegisterTxService method.
func (app *SimApp)
RegisterTxService(clientCtx client.Context) {
authtx.RegisterTxService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.BaseApp.Simulate, app.interfaceRegistry)
}
/ RegisterTendermintService implements the Application.RegisterTendermintService method.
func (app *SimApp)
RegisterTendermintService(clientCtx client.Context) {
cmtApp := server.NewCometABCIWrapper(app)
cmtservice.RegisterTendermintService(
clientCtx,
app.BaseApp.GRPCQueryRouter(),
app.interfaceRegistry,
cmtApp.Query,
)
}
func (app *SimApp)
RegisterNodeService(clientCtx client.Context, cfg config.Config) {
nodeservice.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg)
}
/ GetMaccPerms returns a copy of the module account permissions
/
/ NOTE: This is solely to be used for testing purposes.
func GetMaccPerms()
map[string][]string {
return maps.Clone(maccPerms)
}
/ BlockedAddresses returns all the app's blocked account addresses.
func BlockedAddresses()
map[string]bool {
modAccAddrs := make(map[string]bool)
for acc := range GetMaccPerms() {
modAccAddrs[authtypes.NewModuleAddress(acc).String()] = true
}
/ allow the following addresses to receive funds
delete(modAccAddrs, authtypes.NewModuleAddress(govtypes.ModuleName).String())
return modAccAddrs
}
ModuleManager
should be different in the SimulationManager
.
Finally, the application should expose the SimulationManager
via the following method defined in the Runtime
interface:
Copy
Ask AI
/ SimulationManager implements the SimulationApp interface
func (app *SimApp)
SimulationManager() *module.SimulationManager {
return app.sm
}
Running Simulations
To run the simulation, use thesimsx
runner.
Call the following function from the simsx
package to begin simulating with a default seed:
Copy
Ask AI
package simsx
import (
"encoding/json"
"fmt"
"io"
"math"
"os"
"path/filepath"
"strings"
"testing"
dbm "github.com/cosmos/cosmos-db"
"github.com/stretchr/testify/require"
"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"
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.AppI
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](/docs/sdk/next/documentation/operations/
t *testing.T,
appFactory func(
logger log.Logger,
db dbm.DB,
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], accs []simtypes.Account),
) {
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](/docs/sdk/next/documentation/operations/
t *testing.T,
appFactory func(
logger log.Logger,
db dbm.DB,
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], accs []simtypes.Account),
) {
t.Helper()
RunWithSeedsAndRandAcc(t, appFactory, setupStateFactory, seeds, fuzzSeed, simtypes.RandomAccounts, postRunActions...)
}
/ RunWithSeedsAndRandAcc calls RunWithSeeds with randAccFn
func RunWithSeedsAndRandAcc[T SimulationApp](/docs/sdk/next/documentation/operations/
t *testing.T,
appFactory func(
logger log.Logger,
db dbm.DB,
traceStore io.Writer,
loadLatest bool,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
)
T,
setupStateFactory func(app T)
SimStateFactory,
seeds []int64,
fuzzSeed []byte,
randAccFn simtypes.RandomAccountFn,
postRunActions ...func(t testing.TB, app TestInstance[T], accs []simtypes.Account),
) {
t.Helper()
if deprecatedParams := cli.GetDeprecatedFlagUsed(); len(deprecatedParams) != 0 {
fmt.Printf("Warning: Deprecated flag are used: %s", strings.Join(deprecatedParams, ","))
}
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](/docs/sdk/next/documentation/operations/
tb testing.TB,
cfg simtypes.Config,
appFactory func(logger log.Logger, db dbm.DB, 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], accs []simtypes.Account),
) {
tb.Helper()
RunWithSeedAndRandAcc(tb, cfg, appFactory, setupStateFactory, seed, fuzzSeed, simtypes.RandomAccounts, postRunActions...)
}
/ RunWithSeedAndRandAcc calls RunWithSeed with randAccFn
func RunWithSeedAndRandAcc[T SimulationApp](/docs/sdk/next/documentation/operations/
tb testing.TB,
cfg simtypes.Config,
appFactory func(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp))
T,
setupStateFactory func(app T)
SimStateFactory,
seed int64,
fuzzSeed []byte,
randAccFn simtypes.RandomAccountFn,
postRunActions ...func(t testing.TB, app TestInstance[T], accs []simtypes.Account),
) {
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, accs, err := simulation.SimulateFromSeedX(
tb,
runLogger,
WriteToDebugLog(runLogger),
app.GetBaseApp(),
stateFactory.AppStateFn,
randAccFn,
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 {
simtestutil.PrintStats(testInstance.DB)
}
/ 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, accs)
}
require.NoError(tb, app.Close())
}
type (
HasWeightedOperationsX interface {
WeightedOperationsX(weight WeightSource, reg Registry)
}
HasWeightedOperationsXWithProposals interface {
WeightedOperationsX(weights WeightSource, reg Registry, proposals WeightedProposalMsgIter,
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 dbm.DB
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
simState := module.SimulationState{
AppParams: make(simtypes.AppParams),
Cdc: cdc,
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)
wContent := make([]simtypes.WeightedProposalContent, 0) /nolint:staticcheck / required for legacy type
legacyPReg := NewWeightedFactoryMethods()
/ add gov proposals types
for _, m := range sm.Modules {
switch xm := m.(type) {
case HasProposalMsgsX:
xm.ProposalMsgsX(weights, pReg)
case HasLegacyProposalMsgs:
for _, p := range xm.ProposalMsgs(simState) {
weight := weights.Get(p.AppParamsKey(), safeUint(p.DefaultWeight()))
legacyPReg.Add(weight, legacyToMsgFactoryAdapter(p.MsgSimulatorFn()))
}
case HasLegacyProposalContents:
wContent = append(wContent, xm.ProposalContents(simState)...)
}
}
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, AppendIterators(legacyPReg.Iterator(), pReg.Iterator()), wContent)
case HasLegacyWeightedOperations:
wOps = append(wOps, xm.WeightedOperations(simState)...)
}
}
return append(wOps, Collect(oReg.items, func(a weightedOperation)
simtypes.WeightedOperation {
return a
})...), reporter
}
func safeUint(p int)
uint32 {
if p < 0 || p > math.MaxUint32 {
panic(fmt.Sprintf("can not cast to uint32: %d", p))
}
return uint32(p)
}
/ 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](/docs/sdk/next/documentation/operations/
tb testing.TB,
tCfg simtypes.Config,
appFactory func(logger log.Logger, db dbm.DB, 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"), 0o750))
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
opts := []func(*baseapp.BaseApp) {
baseapp.SetChainID(tCfg.ChainID)
}
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
})
}
/ FauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of
/ an IAVLStore for faster simulation speed.
func FauxMerkleModeOpt(bapp *baseapp.BaseApp) {
bapp.SetFauxMerkleMode()
}
RunWithSeed
:
Copy
Ask AI
package simsx
import (
"encoding/json"
"fmt"
"io"
"math"
"os"
"path/filepath"
"strings"
"testing"
dbm "github.com/cosmos/cosmos-db"
"github.com/stretchr/testify/require"
"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"
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.AppI
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](/docs/sdk/next/documentation/operations/
t *testing.T,
appFactory func(
logger log.Logger,
db dbm.DB,
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], accs []simtypes.Account),
) {
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](/docs/sdk/next/documentation/operations/
t *testing.T,
appFactory func(
logger log.Logger,
db dbm.DB,
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], accs []simtypes.Account),
) {
t.Helper()
RunWithSeedsAndRandAcc(t, appFactory, setupStateFactory, seeds, fuzzSeed, simtypes.RandomAccounts, postRunActions...)
}
/ RunWithSeedsAndRandAcc calls RunWithSeeds with randAccFn
func RunWithSeedsAndRandAcc[T SimulationApp](/docs/sdk/next/documentation/operations/
t *testing.T,
appFactory func(
logger log.Logger,
db dbm.DB,
traceStore io.Writer,
loadLatest bool,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
)
T,
setupStateFactory func(app T)
SimStateFactory,
seeds []int64,
fuzzSeed []byte,
randAccFn simtypes.RandomAccountFn,
postRunActions ...func(t testing.TB, app TestInstance[T], accs []simtypes.Account),
) {
t.Helper()
if deprecatedParams := cli.GetDeprecatedFlagUsed(); len(deprecatedParams) != 0 {
fmt.Printf("Warning: Deprecated flag are used: %s", strings.Join(deprecatedParams, ","))
}
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](/docs/sdk/next/documentation/operations/
tb testing.TB,
cfg simtypes.Config,
appFactory func(logger log.Logger, db dbm.DB, 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], accs []simtypes.Account),
) {
tb.Helper()
RunWithSeedAndRandAcc(tb, cfg, appFactory, setupStateFactory, seed, fuzzSeed, simtypes.RandomAccounts, postRunActions...)
}
/ RunWithSeedAndRandAcc calls RunWithSeed with randAccFn
func RunWithSeedAndRandAcc[T SimulationApp](/docs/sdk/next/documentation/operations/
tb testing.TB,
cfg simtypes.Config,
appFactory func(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp))
T,
setupStateFactory func(app T)
SimStateFactory,
seed int64,
fuzzSeed []byte,
randAccFn simtypes.RandomAccountFn,
postRunActions ...func(t testing.TB, app TestInstance[T], accs []simtypes.Account),
) {
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, accs, err := simulation.SimulateFromSeedX(
tb,
runLogger,
WriteToDebugLog(runLogger),
app.GetBaseApp(),
stateFactory.AppStateFn,
randAccFn,
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 {
simtestutil.PrintStats(testInstance.DB)
}
/ 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, accs)
}
require.NoError(tb, app.Close())
}
type (
HasWeightedOperationsX interface {
WeightedOperationsX(weight WeightSource, reg Registry)
}
HasWeightedOperationsXWithProposals interface {
WeightedOperationsX(weights WeightSource, reg Registry, proposals WeightedProposalMsgIter,
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 dbm.DB
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
simState := module.SimulationState{
AppParams: make(simtypes.AppParams),
Cdc: cdc,
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)
wContent := make([]simtypes.WeightedProposalContent, 0) /nolint:staticcheck / required for legacy type
legacyPReg := NewWeightedFactoryMethods()
/ add gov proposals types
for _, m := range sm.Modules {
switch xm := m.(type) {
case HasProposalMsgsX:
xm.ProposalMsgsX(weights, pReg)
case HasLegacyProposalMsgs:
for _, p := range xm.ProposalMsgs(simState) {
weight := weights.Get(p.AppParamsKey(), safeUint(p.DefaultWeight()))
legacyPReg.Add(weight, legacyToMsgFactoryAdapter(p.MsgSimulatorFn()))
}
case HasLegacyProposalContents:
wContent = append(wContent, xm.ProposalContents(simState)...)
}
}
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, AppendIterators(legacyPReg.Iterator(), pReg.Iterator()), wContent)
case HasLegacyWeightedOperations:
wOps = append(wOps, xm.WeightedOperations(simState)...)
}
}
return append(wOps, Collect(oReg.items, func(a weightedOperation)
simtypes.WeightedOperation {
return a
})...), reporter
}
func safeUint(p int)
uint32 {
if p < 0 || p > math.MaxUint32 {
panic(fmt.Sprintf("can not cast to uint32: %d", p))
}
return uint32(p)
}
/ 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](/docs/sdk/next/documentation/operations/
tb testing.TB,
tCfg simtypes.Config,
appFactory func(logger log.Logger, db dbm.DB, 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"), 0o750))
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
opts := []func(*baseapp.BaseApp) {
baseapp.SetChainID(tCfg.ChainID)
}
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
})
}
/ FauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of
/ an IAVLStore for faster simulation speed.
func FauxMerkleModeOpt(bapp *baseapp.BaseApp) {
bapp.SetFauxMerkleMode()
}
Copy
Ask AI
/go:build sims
package simapp
import (
"encoding/binary"
"encoding/json"
"flag"
"io"
"math/rand"
"strings"
"sync"
"testing"
abci "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"cosmossdk.io/log"
"cosmossdk.io/store"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/baseapp"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sims "github.com/cosmos/cosmos-sdk/testutil/simsx"
sdk "github.com/cosmos/cosmos-sdk/types"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
"github.com/cosmos/cosmos-sdk/x/feegrant"
"github.com/cosmos/cosmos-sdk/x/simulation"
simcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
var FlagEnableStreamingValue bool
/ Get flags every time the simulator is run
func init() {
simcli.GetSimulatorFlags()
flag.BoolVar(&FlagEnableStreamingValue, "EnableStreaming", false, "Enable streaming service")
}
/ interBlockCacheOpt returns a BaseApp option function that sets the persistent
/ inter-block write-through cache.
func interBlockCacheOpt()
func(*baseapp.BaseApp) {
return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager())
}
func TestFullAppSimulation(t *testing.T) {
sims.Run(t, NewSimApp, setupStateFactory)
}
func setupStateFactory(app *SimApp)
sims.SimStateFactory {
return sims.SimStateFactory{
Codec: app.AppCodec(),
AppStateFn: simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
BlockedAddr: BlockedAddresses(),
AccountSource: app.AccountKeeper,
BalanceSource: app.BankKeeper,
}
}
var (
exportAllModules = []string{
}
exportWithValidatorSet = []string{
}
)
func TestAppImportExport(t *testing.T) {
sims.Run(t, NewSimApp, setupStateFactory, func(tb testing.TB, ti sims.TestInstance[*SimApp], accs []simtypes.Account) {
tb.Helper()
app := ti.App
tb.Log("exporting genesis...\n")
exported, err := app.ExportAppStateAndValidators(false, exportWithValidatorSet, exportAllModules)
require.NoError(tb, err)
tb.Log("importing genesis...\n")
newTestInstance := sims.NewSimulationAppInstance(tb, ti.Cfg, NewSimApp)
newApp := newTestInstance.App
var genesisState GenesisState
require.NoError(tb, json.Unmarshal(exported.AppState, &genesisState))
ctxB := newApp.NewContextLegacy(true, cmtproto.Header{
Height: app.LastBlockHeight()
})
_, err = newApp.ModuleManager.InitGenesis(ctxB, newApp.appCodec, genesisState)
if IsEmptyValidatorSetErr(err) {
tb.Skip("Skipping simulation as all validators have been unbonded")
return
}
require.NoError(tb, err)
err = newApp.StoreConsensusParams(ctxB, exported.ConsensusParams)
require.NoError(tb, err)
tb.Log("comparing stores...")
/ skip certain prefixes
skipPrefixes := map[string][][]byte{
stakingtypes.StoreKey: {
stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey,
stakingtypes.HistoricalInfoKey, stakingtypes.UnbondingIDKey, stakingtypes.UnbondingIndexKey,
stakingtypes.UnbondingTypeKey,
stakingtypes.ValidatorUpdatesKey, / todo (Alex): double check why there is a diff with test-sim-import-export
},
authzkeeper.StoreKey: {
authzkeeper.GrantQueuePrefix
},
feegrant.StoreKey: {
feegrant.FeeAllowanceQueueKeyPrefix
},
slashingtypes.StoreKey: {
slashingtypes.ValidatorMissedBlockBitmapKeyPrefix
},
}
AssertEqualStores(tb, app, newApp, app.SimulationManager().StoreDecoders, skipPrefixes)
})
}
/ Scenario:
/
/ Start a fresh node and run n blocks, export state
/ 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(tb testing.TB, ti sims.TestInstance[*SimApp], accs []simtypes.Account) {
tb.Helper()
app := ti.App
tb.Log("exporting genesis...\n")
exported, err := app.ExportAppStateAndValidators(false, exportWithValidatorSet, exportAllModules)
require.NoError(tb, err)
tb.Log("importing genesis...\n")
newTestInstance := sims.NewSimulationAppInstance(tb, ti.Cfg, NewSimApp)
newApp := newTestInstance.App
_, err = newApp.InitChain(&abci.RequestInitChain{
AppStateBytes: exported.AppState,
ChainId: sims.SimAppChainID,
})
if IsEmptyValidatorSetErr(err) {
tb.Skip("Skipping simulation as all validators have been unbonded")
return
}
require.NoError(tb, err)
newStateFactory := setupStateFactory(newApp)
_, _, err = simulation.SimulateFromSeedX(
tb,
newTestInstance.AppLogger,
sims.WriteToDebugLog(newTestInstance.AppLogger),
newApp.BaseApp,
newStateFactory.AppStateFn,
simtypes.RandomAccounts,
simtestutil.BuildSimulationOperations(newApp, newApp.AppCodec(), newTestInstance.Cfg, newApp.TxConfig()),
newStateFactory.BlockedAddr,
newTestInstance.Cfg,
newStateFactory.Codec,
ti.ExecLogWriter,
)
require.NoError(tb, err)
})
}
func IsEmptyValidatorSetErr(err error)
bool {
return err != nil && strings.Contains(err.Error(), "validator set is empty after InitGenesis")
}
func TestAppStateDeterminism(t *testing.T) {
const numTimesToRunPerSeed = 3
var seeds []int64
if s := simcli.NewConfigFromFlags().Seed; s != simcli.DefaultSeedValue {
/ We will be overriding the random seed and just run a single simulation on the provided seed value
for j := 0; j < numTimesToRunPerSeed; j++ { / multiple rounds
seeds = append(seeds, s)
}
}
else {
/ setup with 3 random seeds
for i := 0; i < 3; i++ {
seed := rand.Int63()
for j := 0; j < numTimesToRunPerSeed; j++ { / multiple rounds
seeds = append(seeds, seed)
}
}
}
/ overwrite default app config
interBlockCachingAppFactory := func(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp)) *SimApp {
if FlagEnableStreamingValue {
m := map[string]any{
"streaming.abci.keys": []string{"*"
},
"streaming.abci.plugin": "abci_v1",
"streaming.abci.stop-node-on-err": true,
}
others := appOpts
appOpts = appOptionsFn(func(k string)
any {
if v, ok := m[k]; ok {
return v
}
return others.Get(k)
})
}
return NewSimApp(logger, db, nil, true, appOpts, append(baseAppOptions, interBlockCacheOpt())...)
}
var mx sync.Mutex
appHashResults := make(map[int64][][]byte)
appSimLogger := make(map[int64][]simulation.LogWriter)
captureAndCheckHash := func(tb testing.TB, ti sims.TestInstance[*SimApp], _ []simtypes.Account) {
tb.Helper()
seed, appHash := ti.Cfg.Seed, ti.App.LastCommitID().Hash
mx.Lock()
otherHashes, execWriters := appHashResults[seed], appSimLogger[seed]
if len(otherHashes) < numTimesToRunPerSeed-1 {
appHashResults[seed], appSimLogger[seed] = append(otherHashes, appHash), append(execWriters, ti.ExecLogWriter)
}
else { / cleanup
delete(appHashResults, seed)
delete(appSimLogger, seed)
}
mx.Unlock()
var failNow bool
/ and check that all app hashes per seed are equal for each iteration
for i := 0; i < len(otherHashes); i++ {
if !assert.Equal(tb, otherHashes[i], appHash) {
execWriters[i].PrintLogs()
failNow = true
}
}
if failNow {
ti.ExecLogWriter.PrintLogs()
tb.Fatalf("non-determinism in seed %d", seed)
}
}
/ run simulations
sims.RunWithSeeds(t, interBlockCachingAppFactory, setupStateFactory, seeds, []byte{
}, captureAndCheckHash)
}
type ComparableStoreApp interface {
LastBlockHeight()
int64
NewContextLegacy(isCheckTx bool, header cmtproto.Header)
sdk.Context
GetKey(storeKey string) *storetypes.KVStoreKey
GetStoreKeys() []storetypes.StoreKey
}
func AssertEqualStores(
tb testing.TB,
app, newApp ComparableStoreApp,
storeDecoders simtypes.StoreDecoderRegistry,
skipPrefixes map[string][][]byte,
) {
tb.Helper()
ctxA := app.NewContextLegacy(true, cmtproto.Header{
Height: app.LastBlockHeight()
})
ctxB := newApp.NewContextLegacy(true, cmtproto.Header{
Height: app.LastBlockHeight()
})
storeKeys := app.GetStoreKeys()
require.NotEmpty(tb, storeKeys)
for _, appKeyA := range storeKeys {
/ only compare kvstores
if _, ok := appKeyA.(*storetypes.KVStoreKey); !ok {
continue
}
keyName := appKeyA.Name()
appKeyB := newApp.GetKey(keyName)
storeA := ctxA.KVStore(appKeyA)
storeB := ctxB.KVStore(appKeyB)
failedKVAs, failedKVBs := simtestutil.DiffKVStores(storeA, storeB, skipPrefixes[keyName])
require.Equal(tb, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare %s, key stores %s and %s", keyName, appKeyA, appKeyB)
tb.Logf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), appKeyA, appKeyB)
if !assert.Equal(tb, 0, len(failedKVAs), simtestutil.GetSimulationLog(keyName, storeDecoders, failedKVAs, failedKVBs)) {
for _, v := range failedKVAs {
tb.Logf("store mismatch: %q\n", v)
}
tb.FailNow()
}
}
}
/ 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)
}
/ FauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of
/ an IAVLStore for faster simulation speed.
func FauxMerkleModeOpt(bapp *baseapp.BaseApp) {
bapp.SetFauxMerkleMode()
}
func FuzzFullAppSimulation(f *testing.F) {
f.Fuzz(func(t *testing.T, rawSeed []byte) {
if len(rawSeed) < 8 {
t.Skip()
return
}
sims.RunWithSeeds(
t,
NewSimApp,
setupStateFactory,
[]int64{
int64(binary.BigEndian.Uint64(rawSeed))
},
rawSeed[8:],
)
})
}