diff --git a/app/app.go b/app/app.go index b6ba2ddab9..644a7281be 100644 --- a/app/app.go +++ b/app/app.go @@ -515,7 +515,7 @@ func NewWasmApp( distr.NewAppModule(appCodec, app.distrKeeper, app.accountKeeper, app.bankKeeper, app.stakingKeeper), staking.NewAppModule(appCodec, app.stakingKeeper, app.accountKeeper, app.bankKeeper), upgrade.NewAppModule(app.upgradeKeeper), - wasm.NewAppModule(appCodec, &app.wasmKeeper, app.stakingKeeper), + wasm.NewAppModule(appCodec, &app.wasmKeeper, app.stakingKeeper, app.accountKeeper, app.bankKeeper), evidence.NewAppModule(app.evidenceKeeper), feegrantmodule.NewAppModule(appCodec, app.accountKeeper, app.bankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.accountKeeper, app.bankKeeper, app.interfaceRegistry), @@ -632,7 +632,7 @@ func NewWasmApp( slashing.NewAppModule(appCodec, app.slashingKeeper, app.accountKeeper, app.bankKeeper, app.stakingKeeper), params.NewAppModule(app.paramsKeeper), evidence.NewAppModule(app.evidenceKeeper), - wasm.NewAppModule(appCodec, &app.wasmKeeper, app.stakingKeeper), + wasm.NewAppModule(appCodec, &app.wasmKeeper, app.stakingKeeper, app.accountKeeper, app.bankKeeper), ibc.NewAppModule(app.ibcKeeper), transferModule, ) diff --git a/app/params/weights.go b/app/params/weights.go index 0ba377b009..797996603a 100644 --- a/app/params/weights.go +++ b/app/params/weights.go @@ -20,4 +20,6 @@ const ( DefaultWeightCommunitySpendProposal int = 5 DefaultWeightTextProposal int = 5 DefaultWeightParamChangeProposal int = 5 + DefaultWeightMsgStoreCode int = 100 + DefaultWeightMsgInstantiateContract int = 100 ) diff --git a/x/wasm/module.go b/x/wasm/module.go index 9453cf09a0..9f078bb12f 100644 --- a/x/wasm/module.go +++ b/x/wasm/module.go @@ -13,6 +13,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + simKeeper "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cast" @@ -103,6 +104,8 @@ type AppModule struct { cdc codec.Codec keeper *Keeper validatorSetSource keeper.ValidatorSetSource + accountKeeper types.AccountKeeper // for simulation + bankKeeper simKeeper.BankKeeper } // ConsensusVersion is a sequence number for state-breaking change of the @@ -112,12 +115,20 @@ type AppModule struct { func (AppModule) ConsensusVersion() uint64 { return 1 } // NewAppModule creates a new AppModule object -func NewAppModule(cdc codec.Codec, keeper *Keeper, validatorSetSource keeper.ValidatorSetSource) AppModule { +func NewAppModule( + cdc codec.Codec, + keeper *Keeper, + validatorSetSource keeper.ValidatorSetSource, + ak types.AccountKeeper, + bk simKeeper.BankKeeper, +) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, cdc: cdc, keeper: keeper, validatorSetSource: validatorSetSource, + accountKeeper: ak, + bankKeeper: bk, } } @@ -196,7 +207,7 @@ func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { // WeightedOperations returns the all the gov module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return nil + return simulation.WeightedOperations(&simState, am.accountKeeper, am.bankKeeper, am.keeper) } // ____________________________________________________________________________ diff --git a/x/wasm/module_test.go b/x/wasm/module_test.go index 360743a125..ff39b27521 100644 --- a/x/wasm/module_test.go +++ b/x/wasm/module_test.go @@ -36,7 +36,7 @@ func setupTest(t *testing.T) testData { ctx, keepers := CreateTestInput(t, false, "iterator,staking,stargate") cdc := keeper.MakeTestCodec(t) data := testData{ - module: NewAppModule(cdc, keepers.WasmKeeper, keepers.StakingKeeper), + module: NewAppModule(cdc, keepers.WasmKeeper, keepers.StakingKeeper, keepers.AccountKeeper, keepers.BankKeeper), ctx: ctx, acctKeeper: keepers.AccountKeeper, keeper: *keepers.WasmKeeper, diff --git a/x/wasm/simulation/operations.go b/x/wasm/simulation/operations.go new file mode 100644 index 0000000000..3793a15d61 --- /dev/null +++ b/x/wasm/simulation/operations.go @@ -0,0 +1,163 @@ +package simulation + +import ( + _ "embed" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + simappparams "github.com/cosmos/cosmos-sdk/simapp/params" + 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/CosmWasm/wasmd/app/params" + "github.com/CosmWasm/wasmd/x/wasm/types" +) + +//go:embed testdata/reflect.wasm +var reflectContract []byte + +// Simulation operation weights constants +//nolint:gosec +const ( + OpWeightMsgStoreCode = "op_weight_msg_store_code" + OpWeightMsgInstantiateContract = "op_weight_msg_instantiate_contract" +) + +// WasmKeeper is a subset of the wasm keeper used by simulations +type WasmKeeper interface { + GetParams(ctx sdk.Context) types.Params + IterateCodeInfos(ctx sdk.Context, cb func(uint64, types.CodeInfo) bool) +} + +// WeightedOperations returns all the operations from the module with their respective weights +func WeightedOperations( + simstate *module.SimulationState, + ak types.AccountKeeper, + bk simulation.BankKeeper, + wasmKeeper WasmKeeper, +) simulation.WeightedOperations { + var ( + weightMsgStoreCode int + weightMsgInstantiateContract int + ) + + simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgStoreCode, &weightMsgStoreCode, nil, + func(_ *rand.Rand) { + weightMsgStoreCode = params.DefaultWeightMsgStoreCode + }, + ) + + simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgInstantiateContract, &weightMsgInstantiateContract, nil, + func(_ *rand.Rand) { + weightMsgInstantiateContract = params.DefaultWeightMsgInstantiateContract + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgStoreCode, + SimulateMsgStoreCode(ak, bk, wasmKeeper), + ), + simulation.NewWeightedOperation( + weightMsgInstantiateContract, + SimulateMsgInstantiateContract(ak, bk, wasmKeeper), + ), + } +} + +// SimulateMsgStoreCode generates a MsgStoreCode with random values +func SimulateMsgStoreCode(ak types.AccountKeeper, bk simulation.BankKeeper, wasmKeeper WasmKeeper) simtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + if wasmKeeper.GetParams(ctx).CodeUploadAccess.Permission != types.AccessTypeEverybody { + return simtypes.NoOpMsg(types.ModuleName, types.MsgStoreCode{}.Type(), "no chain permission"), nil, nil + } + + config := &types.AccessConfig{ + Permission: types.AccessTypeEverybody, + } + + simAccount, _ := simtypes.RandomAcc(r, accs) + msg := types.MsgStoreCode{ + Sender: simAccount.Address.String(), + WASMByteCode: reflectContract, + InstantiatePermission: config, + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// SimulateMsgInstantiateContract generates a MsgInstantiateContract with random values +func SimulateMsgInstantiateContract(ak types.AccountKeeper, bk simulation.BankKeeper, wasmKeeper WasmKeeper) simtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + simAccount, _ := simtypes.RandomAcc(r, accs) + + var codeID uint64 + wasmKeeper.IterateCodeInfos(ctx, func(u uint64, info types.CodeInfo) bool { + if info.InstantiateConfig.Permission != types.AccessTypeEverybody { + return false + } + codeID = u + return true + }) + + if codeID == 0 { + return simtypes.NoOpMsg(types.ModuleName, types.MsgInstantiateContract{}.Type(), "no codes with permission available"), nil, nil + } + + spendable := bk.SpendableCoins(ctx, simAccount.Address) + + msg := types.MsgInstantiateContract{ + Sender: simAccount.Address.String(), + Admin: simtypes.RandomAccounts(r, 1)[0].Address.String(), + CodeID: codeID, + Label: simtypes.RandStringOfLength(r, 10), + Msg: []byte(`{}`), + Funds: simtypes.RandSubsetCoins(r, spendable), + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/wasm/simulation/testdata/reflect.wasm b/x/wasm/simulation/testdata/reflect.wasm new file mode 100644 index 0000000000..312f45768d Binary files /dev/null and b/x/wasm/simulation/testdata/reflect.wasm differ