Reintroduce memKVStore to keep track of fwd and reverse mappings. On reverse mapping, rather than store a mapping to marshalled capability; we store the index. capability.Keeper and all scopedKeeper have access to a capability map that maps index to the capability pointer. This is done to make sure that all writes to memKVStore get reverted on a fail tx, while also allowing GetCapability to retrieve the original memory pointer from the go map. Go map must be accessed only by first going through the memKVStore. SInce writes to go map cannot be automatically reverted on tx failure, it gets cleaned up on failed GetCapability calls. Closes: #5965
452 lines
17 KiB
Go
452 lines
17 KiB
Go
package simapp
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
|
|
abci "github.com/tendermint/tendermint/abci/types"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
tmos "github.com/tendermint/tendermint/libs/os"
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
codecstd "github.com/cosmos/cosmos-sdk/codec/std"
|
|
"github.com/cosmos/cosmos-sdk/std"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/cosmos/cosmos-sdk/types/module"
|
|
"github.com/cosmos/cosmos-sdk/version"
|
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
|
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
|
"github.com/cosmos/cosmos-sdk/x/capability"
|
|
"github.com/cosmos/cosmos-sdk/x/crisis"
|
|
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
|
"github.com/cosmos/cosmos-sdk/x/evidence"
|
|
"github.com/cosmos/cosmos-sdk/x/genutil"
|
|
"github.com/cosmos/cosmos-sdk/x/gov"
|
|
"github.com/cosmos/cosmos-sdk/x/ibc"
|
|
ibcclient "github.com/cosmos/cosmos-sdk/x/ibc/02-client"
|
|
port "github.com/cosmos/cosmos-sdk/x/ibc/05-port"
|
|
transfer "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer"
|
|
"github.com/cosmos/cosmos-sdk/x/mint"
|
|
"github.com/cosmos/cosmos-sdk/x/params"
|
|
paramsclient "github.com/cosmos/cosmos-sdk/x/params/client"
|
|
paramproposal "github.com/cosmos/cosmos-sdk/x/params/types/proposal"
|
|
"github.com/cosmos/cosmos-sdk/x/slashing"
|
|
"github.com/cosmos/cosmos-sdk/x/staking"
|
|
"github.com/cosmos/cosmos-sdk/x/supply"
|
|
"github.com/cosmos/cosmos-sdk/x/upgrade"
|
|
upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client"
|
|
)
|
|
|
|
const appName = "SimApp"
|
|
|
|
var (
|
|
// DefaultCLIHome default home directories for the application CLI
|
|
DefaultCLIHome = os.ExpandEnv("$HOME/.simapp")
|
|
|
|
// DefaultNodeHome default home directories for the application daemon
|
|
DefaultNodeHome = os.ExpandEnv("$HOME/.simapp")
|
|
|
|
// ModuleBasics defines the module BasicManager is in charge of setting up basic,
|
|
// non-dependant module elements, such as codec registration
|
|
// and genesis verification.
|
|
ModuleBasics = module.NewBasicManager(
|
|
auth.AppModuleBasic{},
|
|
supply.AppModuleBasic{},
|
|
genutil.AppModuleBasic{},
|
|
bank.AppModuleBasic{},
|
|
capability.AppModuleBasic{},
|
|
staking.AppModuleBasic{},
|
|
mint.AppModuleBasic{},
|
|
distr.AppModuleBasic{},
|
|
gov.NewAppModuleBasic(
|
|
paramsclient.ProposalHandler, distr.ProposalHandler, upgradeclient.ProposalHandler,
|
|
),
|
|
params.AppModuleBasic{},
|
|
crisis.AppModuleBasic{},
|
|
slashing.AppModuleBasic{},
|
|
ibc.AppModuleBasic{},
|
|
upgrade.AppModuleBasic{},
|
|
evidence.AppModuleBasic{},
|
|
transfer.AppModuleBasic{},
|
|
)
|
|
|
|
// module account permissions
|
|
maccPerms = map[string][]string{
|
|
auth.FeeCollectorName: nil,
|
|
distr.ModuleName: nil,
|
|
mint.ModuleName: {supply.Minter},
|
|
staking.BondedPoolName: {supply.Burner, supply.Staking},
|
|
staking.NotBondedPoolName: {supply.Burner, supply.Staking},
|
|
gov.ModuleName: {supply.Burner},
|
|
transfer.GetModuleAccountName(): {supply.Minter, supply.Burner},
|
|
}
|
|
|
|
// module accounts that are allowed to receive tokens
|
|
allowedReceivingModAcc = map[string]bool{
|
|
distr.ModuleName: true,
|
|
}
|
|
)
|
|
|
|
var _ App = (*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
|
|
cdc *codec.Codec
|
|
|
|
invCheckPeriod uint
|
|
|
|
// keys to access the substores
|
|
keys map[string]*sdk.KVStoreKey
|
|
tkeys map[string]*sdk.TransientStoreKey
|
|
memKeys map[string]*sdk.MemoryStoreKey
|
|
|
|
// subspaces
|
|
subspaces map[string]params.Subspace
|
|
|
|
// keepers
|
|
AccountKeeper auth.AccountKeeper
|
|
BankKeeper bank.Keeper
|
|
CapabilityKeeper *capability.Keeper
|
|
SupplyKeeper supply.Keeper
|
|
StakingKeeper staking.Keeper
|
|
SlashingKeeper slashing.Keeper
|
|
MintKeeper mint.Keeper
|
|
DistrKeeper distr.Keeper
|
|
GovKeeper gov.Keeper
|
|
CrisisKeeper crisis.Keeper
|
|
UpgradeKeeper upgrade.Keeper
|
|
ParamsKeeper params.Keeper
|
|
IBCKeeper *ibc.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly
|
|
EvidenceKeeper evidence.Keeper
|
|
TransferKeeper transfer.Keeper
|
|
|
|
// make scoped keepers public for test purposes
|
|
ScopedIBCKeeper capability.ScopedKeeper
|
|
ScopedTransferKeeper capability.ScopedKeeper
|
|
|
|
// the module manager
|
|
mm *module.Manager
|
|
|
|
// simulation manager
|
|
sm *module.SimulationManager
|
|
}
|
|
|
|
// NewSimApp returns a reference to an initialized SimApp.
|
|
func NewSimApp(
|
|
logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, skipUpgradeHeights map[int64]bool,
|
|
homePath string, invCheckPeriod uint, baseAppOptions ...func(*baseapp.BaseApp),
|
|
) *SimApp {
|
|
|
|
// TODO: Remove cdc in favor of appCodec once all modules are migrated.
|
|
cdc := codecstd.MakeCodec(ModuleBasics)
|
|
appCodec := codecstd.NewAppCodec(cdc)
|
|
|
|
bApp := baseapp.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...)
|
|
bApp.SetCommitMultiStoreTracer(traceStore)
|
|
bApp.SetAppVersion(version.Version)
|
|
|
|
keys := sdk.NewKVStoreKeys(
|
|
auth.StoreKey, bank.StoreKey, staking.StoreKey,
|
|
supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey,
|
|
gov.StoreKey, params.StoreKey, ibc.StoreKey, upgrade.StoreKey,
|
|
evidence.StoreKey, transfer.StoreKey, capability.StoreKey,
|
|
)
|
|
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)
|
|
memKeys := sdk.NewMemoryStoreKeys(capability.MemStoreKey)
|
|
|
|
app := &SimApp{
|
|
BaseApp: bApp,
|
|
cdc: cdc,
|
|
invCheckPeriod: invCheckPeriod,
|
|
keys: keys,
|
|
tkeys: tkeys,
|
|
memKeys: memKeys,
|
|
subspaces: make(map[string]params.Subspace),
|
|
}
|
|
|
|
// init params keeper and subspaces
|
|
app.ParamsKeeper = params.NewKeeper(appCodec, keys[params.StoreKey], tkeys[params.TStoreKey])
|
|
app.subspaces[auth.ModuleName] = app.ParamsKeeper.Subspace(auth.DefaultParamspace)
|
|
app.subspaces[bank.ModuleName] = app.ParamsKeeper.Subspace(bank.DefaultParamspace)
|
|
app.subspaces[staking.ModuleName] = app.ParamsKeeper.Subspace(staking.DefaultParamspace)
|
|
app.subspaces[mint.ModuleName] = app.ParamsKeeper.Subspace(mint.DefaultParamspace)
|
|
app.subspaces[distr.ModuleName] = app.ParamsKeeper.Subspace(distr.DefaultParamspace)
|
|
app.subspaces[slashing.ModuleName] = app.ParamsKeeper.Subspace(slashing.DefaultParamspace)
|
|
app.subspaces[gov.ModuleName] = app.ParamsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable())
|
|
app.subspaces[crisis.ModuleName] = app.ParamsKeeper.Subspace(crisis.DefaultParamspace)
|
|
|
|
// set the BaseApp's parameter store
|
|
bApp.SetParamStore(app.ParamsKeeper.Subspace(baseapp.Paramspace).WithKeyTable(std.ConsensusParamsKeyTable()))
|
|
|
|
// add capability keeper and ScopeToModule for ibc module
|
|
app.CapabilityKeeper = capability.NewKeeper(appCodec, keys[capability.StoreKey], memKeys[capability.MemStoreKey])
|
|
scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibc.ModuleName)
|
|
scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(transfer.ModuleName)
|
|
|
|
// add keepers
|
|
app.AccountKeeper = auth.NewAccountKeeper(
|
|
appCodec, keys[auth.StoreKey], app.subspaces[auth.ModuleName], auth.ProtoBaseAccount,
|
|
)
|
|
app.BankKeeper = bank.NewBaseKeeper(
|
|
appCodec, keys[bank.StoreKey], app.AccountKeeper, app.subspaces[bank.ModuleName], app.BlacklistedAccAddrs(),
|
|
)
|
|
app.SupplyKeeper = supply.NewKeeper(
|
|
appCodec, keys[supply.StoreKey], app.AccountKeeper, app.BankKeeper, maccPerms,
|
|
)
|
|
stakingKeeper := staking.NewKeeper(
|
|
appCodec, keys[staking.StoreKey], app.BankKeeper, app.SupplyKeeper, app.subspaces[staking.ModuleName],
|
|
)
|
|
app.MintKeeper = mint.NewKeeper(
|
|
appCodec, keys[mint.StoreKey], app.subspaces[mint.ModuleName], &stakingKeeper,
|
|
app.SupplyKeeper, auth.FeeCollectorName,
|
|
)
|
|
app.DistrKeeper = distr.NewKeeper(
|
|
appCodec, keys[distr.StoreKey], app.subspaces[distr.ModuleName], app.BankKeeper, &stakingKeeper,
|
|
app.SupplyKeeper, auth.FeeCollectorName, app.ModuleAccountAddrs(),
|
|
)
|
|
app.SlashingKeeper = slashing.NewKeeper(
|
|
appCodec, keys[slashing.StoreKey], &stakingKeeper, app.subspaces[slashing.ModuleName],
|
|
)
|
|
app.CrisisKeeper = crisis.NewKeeper(
|
|
app.subspaces[crisis.ModuleName], invCheckPeriod, app.SupplyKeeper, auth.FeeCollectorName,
|
|
)
|
|
app.UpgradeKeeper = upgrade.NewKeeper(skipUpgradeHeights, keys[upgrade.StoreKey], appCodec, homePath)
|
|
|
|
// register the proposal types
|
|
govRouter := gov.NewRouter()
|
|
govRouter.AddRoute(gov.RouterKey, gov.ProposalHandler).
|
|
AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)).
|
|
AddRoute(distr.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper)).
|
|
AddRoute(upgrade.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper))
|
|
app.GovKeeper = gov.NewKeeper(
|
|
appCodec, keys[gov.StoreKey], app.subspaces[gov.ModuleName], app.SupplyKeeper,
|
|
&stakingKeeper, govRouter,
|
|
)
|
|
|
|
// register the staking hooks
|
|
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
|
|
app.StakingKeeper = *stakingKeeper.SetHooks(
|
|
staking.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()),
|
|
)
|
|
|
|
// Create IBC Keeper
|
|
app.IBCKeeper = ibc.NewKeeper(
|
|
app.cdc, keys[ibc.StoreKey], app.StakingKeeper, scopedIBCKeeper,
|
|
)
|
|
|
|
// Create Transfer Keepers
|
|
app.TransferKeeper = transfer.NewKeeper(
|
|
app.cdc, keys[transfer.StoreKey],
|
|
app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper,
|
|
app.BankKeeper, app.SupplyKeeper,
|
|
scopedTransferKeeper,
|
|
)
|
|
transferModule := transfer.NewAppModule(app.TransferKeeper)
|
|
|
|
// Create static IBC router, add transfer route, then set and seal it
|
|
ibcRouter := port.NewRouter()
|
|
ibcRouter.AddRoute(transfer.ModuleName, transferModule)
|
|
app.IBCKeeper.SetRouter(ibcRouter)
|
|
|
|
// create evidence keeper with router
|
|
evidenceKeeper := evidence.NewKeeper(
|
|
appCodec, keys[evidence.StoreKey], &app.StakingKeeper, app.SlashingKeeper,
|
|
)
|
|
evidenceRouter := evidence.NewRouter().
|
|
AddRoute(ibcclient.RouterKey, ibcclient.HandlerClientMisbehaviour(app.IBCKeeper.ClientKeeper))
|
|
|
|
evidenceKeeper.SetRouter(evidenceRouter)
|
|
app.EvidenceKeeper = *evidenceKeeper
|
|
|
|
// NOTE: Any module instantiated in the module manager that is later modified
|
|
// must be passed by reference here.
|
|
app.mm = module.NewManager(
|
|
genutil.NewAppModule(app.AccountKeeper, app.StakingKeeper, app.BaseApp.DeliverTx),
|
|
auth.NewAppModule(app.AccountKeeper, app.SupplyKeeper),
|
|
bank.NewAppModule(app.BankKeeper, app.AccountKeeper),
|
|
capability.NewAppModule(*app.CapabilityKeeper),
|
|
crisis.NewAppModule(&app.CrisisKeeper),
|
|
supply.NewAppModule(app.SupplyKeeper, app.BankKeeper, app.AccountKeeper),
|
|
gov.NewAppModule(app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.SupplyKeeper),
|
|
mint.NewAppModule(app.MintKeeper, app.SupplyKeeper),
|
|
slashing.NewAppModule(app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
|
|
distr.NewAppModule(app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.SupplyKeeper, app.StakingKeeper),
|
|
staking.NewAppModule(app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.SupplyKeeper),
|
|
upgrade.NewAppModule(app.UpgradeKeeper),
|
|
evidence.NewAppModule(app.EvidenceKeeper),
|
|
ibc.NewAppModule(app.IBCKeeper),
|
|
params.NewAppModule(app.ParamsKeeper),
|
|
transferModule,
|
|
)
|
|
|
|
// 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.
|
|
app.mm.SetOrderBeginBlockers(upgrade.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName, evidence.ModuleName, staking.ModuleName)
|
|
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName)
|
|
|
|
// NOTE: The genutils moodule must occur after staking so that pools are
|
|
// properly initialized with tokens from genesis accounts.
|
|
app.mm.SetOrderInitGenesis(
|
|
auth.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName,
|
|
slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName,
|
|
crisis.ModuleName, ibc.ModuleName, genutil.ModuleName, evidence.ModuleName,
|
|
transfer.ModuleName,
|
|
)
|
|
|
|
app.mm.RegisterInvariants(&app.CrisisKeeper)
|
|
app.mm.RegisterRoutes(app.Router(), app.QueryRouter())
|
|
|
|
// 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
|
|
app.sm = module.NewSimulationManager(
|
|
auth.NewAppModule(app.AccountKeeper, app.SupplyKeeper),
|
|
bank.NewAppModule(app.BankKeeper, app.AccountKeeper),
|
|
supply.NewAppModule(app.SupplyKeeper, app.BankKeeper, app.AccountKeeper),
|
|
gov.NewAppModule(app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.SupplyKeeper),
|
|
mint.NewAppModule(app.MintKeeper, app.SupplyKeeper),
|
|
staking.NewAppModule(app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.SupplyKeeper),
|
|
distr.NewAppModule(app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.SupplyKeeper, app.StakingKeeper),
|
|
slashing.NewAppModule(app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
|
|
params.NewAppModule(app.ParamsKeeper),
|
|
)
|
|
|
|
app.sm.RegisterStoreDecoders()
|
|
|
|
// initialize stores
|
|
app.MountKVStores(keys)
|
|
app.MountTransientStores(tkeys)
|
|
app.MountMemoryStores(memKeys)
|
|
|
|
// initialize BaseApp
|
|
app.SetInitChainer(app.InitChainer)
|
|
app.SetBeginBlocker(app.BeginBlocker)
|
|
app.SetAnteHandler(
|
|
ante.NewAnteHandler(
|
|
app.AccountKeeper, app.SupplyKeeper, *app.IBCKeeper,
|
|
ante.DefaultSigVerificationGasConsumer,
|
|
),
|
|
)
|
|
app.SetEndBlocker(app.EndBlocker)
|
|
|
|
if loadLatest {
|
|
if err := app.LoadLatestVersion(); err != nil {
|
|
tmos.Exit(err.Error())
|
|
}
|
|
}
|
|
|
|
// Initialize and seal the capability keeper so all persistent capabilities
|
|
// are loaded in-memory and prevent any further modules from creating scoped
|
|
// sub-keepers.
|
|
ctx := app.BaseApp.NewContext(true, abci.Header{})
|
|
app.CapabilityKeeper.InitializeAndSeal(ctx)
|
|
|
|
app.ScopedIBCKeeper = scopedIBCKeeper
|
|
app.ScopedTransferKeeper = scopedTransferKeeper
|
|
|
|
return app
|
|
}
|
|
|
|
// Name returns the name of the App
|
|
func (app *SimApp) Name() string { return app.BaseApp.Name() }
|
|
|
|
// BeginBlocker application updates every begin block
|
|
func (app *SimApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
|
|
return app.mm.BeginBlock(ctx, req)
|
|
}
|
|
|
|
// EndBlocker application updates every end block
|
|
func (app *SimApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
|
|
return app.mm.EndBlock(ctx, req)
|
|
}
|
|
|
|
// InitChainer application update at chain initialization
|
|
func (app *SimApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
|
var genesisState GenesisState
|
|
app.cdc.MustUnmarshalJSON(req.AppStateBytes, &genesisState)
|
|
return app.mm.InitGenesis(ctx, app.cdc, genesisState)
|
|
}
|
|
|
|
// LoadHeight loads a particular height
|
|
func (app *SimApp) LoadHeight(height int64) error {
|
|
return app.LoadVersion(height)
|
|
}
|
|
|
|
// ModuleAccountAddrs returns all the app's module account addresses.
|
|
func (app *SimApp) ModuleAccountAddrs() map[string]bool {
|
|
modAccAddrs := make(map[string]bool)
|
|
for acc := range maccPerms {
|
|
modAccAddrs[supply.NewModuleAddress(acc).String()] = true
|
|
}
|
|
|
|
return modAccAddrs
|
|
}
|
|
|
|
// BlacklistedAccAddrs returns all the app's module account addresses black listed for receiving tokens.
|
|
func (app *SimApp) BlacklistedAccAddrs() map[string]bool {
|
|
blacklistedAddrs := make(map[string]bool)
|
|
for acc := range maccPerms {
|
|
blacklistedAddrs[supply.NewModuleAddress(acc).String()] = !allowedReceivingModAcc[acc]
|
|
}
|
|
|
|
return blacklistedAddrs
|
|
}
|
|
|
|
// Codec returns SimApp's 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) Codec() *codec.Codec {
|
|
return app.cdc
|
|
}
|
|
|
|
// 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) *sdk.KVStoreKey {
|
|
return app.keys[storeKey]
|
|
}
|
|
|
|
// GetTKey returns the TransientStoreKey for the provided store key.
|
|
//
|
|
// NOTE: This is solely to be used for testing purposes.
|
|
func (app *SimApp) GetTKey(storeKey string) *sdk.TransientStoreKey {
|
|
return app.tkeys[storeKey]
|
|
}
|
|
|
|
// GetMemKey returns the MemStoreKey for the provided mem key.
|
|
//
|
|
// NOTE: This is solely used for testing purposes.
|
|
func (app *SimApp) GetMemKey(storeKey string) *sdk.MemoryStoreKey {
|
|
return app.memKeys[storeKey]
|
|
}
|
|
|
|
// GetSubspace returns a param subspace for a given module name.
|
|
//
|
|
// NOTE: This is solely to be used for testing purposes.
|
|
func (app *SimApp) GetSubspace(moduleName string) params.Subspace {
|
|
return app.subspaces[moduleName]
|
|
}
|
|
|
|
// SimulationManager implements the SimulationApp interface
|
|
func (app *SimApp) SimulationManager() *module.SimulationManager {
|
|
return app.sm
|
|
}
|
|
|
|
// GetMaccPerms returns a copy of the module account permissions
|
|
func GetMaccPerms() map[string][]string {
|
|
dupMaccPerms := make(map[string][]string)
|
|
for k, v := range maccPerms {
|
|
dupMaccPerms[k] = v
|
|
}
|
|
return dupMaccPerms
|
|
}
|