From 2a9c5c1ea542d3d968bebeea7031a7d0a4e38ffa Mon Sep 17 00:00:00 2001 From: Alexander Peters Date: Tue, 31 May 2022 10:07:16 +0200 Subject: [PATCH] Fix wasm simulations (#870) * Fix wasm simulations + make functions compatible with tgrade * Fix lint issues * Fix simulation setup * Make simulations store msg pass * Use default values params to make operations succeed * Normalize wasm store * Add simulations to circleci config * Run simulation in temp dir * Store sim logs * Increase circleci machine type * Extract reflect contract api into helper * Add execute msg to simulations * Embed refect wasm contract Co-authored-by: Pino' Surace --- .circleci/config.yml | 16 ++ app/app.go | 1 - app/params/weights.go | 3 +- app/sim_test.go | 55 ++++++- x/wasm/keeper/reflect_test.go | 145 +++++------------ x/wasm/keeper/staking_test.go | 35 ++-- x/wasm/keeper/submsg_test.go | 56 +++---- x/wasm/keeper/test_common.go | 13 +- x/wasm/keeper/testdata/reflect.go | 62 +++++++ x/wasm/module.go | 5 +- x/wasm/module_test.go | 4 +- x/wasm/simulation/genesis.go | 2 +- x/wasm/simulation/operations.go | 257 +++++++++++++++++++++++++----- x/wasm/simulation/params.go | 12 +- x/wasm/simulation/sim_utils.go | 53 ++++++ 15 files changed, 499 insertions(+), 220 deletions(-) create mode 100644 x/wasm/keeper/testdata/reflect.go create mode 100644 x/wasm/simulation/sim_utils.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 6de8b269..f1d9838e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -116,6 +116,19 @@ jobs: cd ./benchmarks go test -bench . + simulations: + executor: golang + parallelism: 1 + resource_class: large + steps: + - checkout + - run: + name: Run simulations + command: | + make test-sim-multi-seed-short + - store_artifacts: + path: /tmp + upload-coverage: executor: golang steps: @@ -218,3 +231,6 @@ workflows: - benchmark: requires: - test-cover + - simulations: + requires: + - setup-dependencies diff --git a/app/app.go b/app/app.go index 25ea186c..300ef7b1 100644 --- a/app/app.go +++ b/app/app.go @@ -458,7 +458,6 @@ func NewWasmApp( transferModule := transfer.NewAppModule(app.transferKeeper) transferIBCModule := transfer.NewIBCModule(app.transferKeeper) - _ = app.getSubspace(icahosttypes.SubModuleName) app.icaHostKeeper = icahostkeeper.NewKeeper( appCodec, keys[icahosttypes.StoreKey], diff --git a/app/params/weights.go b/app/params/weights.go index 79799660..9cb2dd39 100644 --- a/app/params/weights.go +++ b/app/params/weights.go @@ -20,6 +20,7 @@ const ( DefaultWeightCommunitySpendProposal int = 5 DefaultWeightTextProposal int = 5 DefaultWeightParamChangeProposal int = 5 - DefaultWeightMsgStoreCode int = 100 + DefaultWeightMsgStoreCode int = 50 DefaultWeightMsgInstantiateContract int = 100 + DefaultWeightMsgExecuteContract int = 100 ) diff --git a/app/sim_test.go b/app/sim_test.go index d8cf98da..d64488de 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -6,11 +6,17 @@ import ( "os" "path/filepath" "testing" + "time" + + "github.com/cosmos/cosmos-sdk/store" + "github.com/cosmos/cosmos-sdk/store/prefix" "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" @@ -118,7 +124,7 @@ func TestAppImportExport(t *testing.T) { t, os.Stdout, app.BaseApp, - simapp.AppStateFn(app.AppCodec(), app.SimulationManager()), + AppStateFn(app.AppCodec(), app.SimulationManager()), simtypes.RandomAccounts, simapp.SimulationOperations(app, app.AppCodec(), config), app.ModuleAccountAddrs(), @@ -190,6 +196,37 @@ func TestAppImportExport(t *testing.T) { // delete persistent tx counter value ctxA.KVStore(app.keys[wasm.StoreKey]).Delete(wasmtypes.TXCounterPrefix) + // reset contract code index in source DB for comparison with dest DB + dropContractHistory := func(s store.KVStore, keys ...[]byte) { + for _, key := range keys { + prefixStore := prefix.NewStore(s, key) + iter := prefixStore.Iterator(nil, nil) + for ; iter.Valid(); iter.Next() { + prefixStore.Delete(iter.Key()) + } + iter.Close() + } + } + prefixes := [][]byte{wasmtypes.ContractCodeHistoryElementPrefix, wasmtypes.ContractByCodeIDAndCreatedSecondaryIndexPrefix} + dropContractHistory(ctxA.KVStore(app.keys[wasm.StoreKey]), prefixes...) + dropContractHistory(ctxB.KVStore(newApp.keys[wasm.StoreKey]), prefixes...) + + normalizeContractInfo := func(ctx sdk.Context, app *WasmApp) { + var index uint64 + app.wasmKeeper.IterateContractInfo(ctx, func(address sdk.AccAddress, info wasmtypes.ContractInfo) bool { + created := &wasmtypes.AbsoluteTxPosition{ + BlockHeight: uint64(0), + TxIndex: index, + } + info.Created = created + store := ctx.KVStore(app.keys[wasm.StoreKey]) + store.Set(wasmtypes.GetContractAddressKey(address), app.appCodec.MustMarshal(&info)) + index++ + return false + }) + } + normalizeContractInfo(ctxA, app) + normalizeContractInfo(ctxB, newApp) // diff both stores for _, skp := range storeKeysPrefixes { storeA := ctxA.KVStore(skp.A) @@ -215,7 +252,7 @@ func TestFullAppSimulation(t *testing.T) { require.NoError(t, os.RemoveAll(dir)) }() encConf := MakeEncodingConfig() - app := NewWasmApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, + app := NewWasmApp(logger, db, nil, true, map[int64]bool{}, t.TempDir(), simapp.FlagPeriodValue, encConf, wasm.EnableAllProposals, simapp.EmptyAppOptions{}, nil, fauxMerkleModeOpt) require.Equal(t, "WasmApp", app.Name()) @@ -224,7 +261,7 @@ func TestFullAppSimulation(t *testing.T) { t, os.Stdout, app.BaseApp, - simapp.AppStateFn(app.appCodec, app.SimulationManager()), + AppStateFn(app.appCodec, app.SimulationManager()), simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 simapp.SimulationOperations(app, app.AppCodec(), config), app.ModuleAccountAddrs(), @@ -241,3 +278,15 @@ func TestFullAppSimulation(t *testing.T) { simapp.PrintStats(db) } } + +// AppStateFn returns the initial application state using a genesis or the simulation parameters. +// It panics if the user provides files for both of them. +// If a file is not given for the genesis or the sim params, it creates a randomized one. +func AppStateFn(codec codec.Codec, manager *module.SimulationManager) simtypes.AppStateFn { + // quick hack to setup app state genesis with our app modules + simapp.ModuleBasics = ModuleBasics + if simapp.FlagGenesisTimeValue == 0 { // always set to have a block time + simapp.FlagGenesisTimeValue = time.Now().Unix() + } + return simapp.AppStateFn(codec, manager) +} diff --git a/x/wasm/keeper/reflect_test.go b/x/wasm/keeper/reflect_test.go index 9b81eb6a..2f89a265 100644 --- a/x/wasm/keeper/reflect_test.go +++ b/x/wasm/keeper/reflect_test.go @@ -6,8 +6,6 @@ import ( "strings" "testing" - "github.com/golang/protobuf/proto" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -16,62 +14,17 @@ import ( authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/CosmWasm/wasmd/x/wasm/keeper/testdata" "github.com/CosmWasm/wasmd/x/wasm/types" ) // ReflectInitMsg is {} -// ReflectHandleMsg is used to encode handle messages -type ReflectHandleMsg struct { - Reflect *reflectPayload `json:"reflect_msg,omitempty"` - ReflectSubMsg *reflectSubPayload `json:"reflect_sub_msg,omitempty"` - Change *ownerPayload `json:"change_owner,omitempty"` -} - -type ownerPayload struct { - Owner sdk.Address `json:"owner"` -} - -type reflectPayload struct { - Msgs []wasmvmtypes.CosmosMsg `json:"msgs"` -} - -type reflectSubPayload struct { - Msgs []wasmvmtypes.SubMsg `json:"msgs"` -} - -// ReflectQueryMsg is used to encode query messages -type ReflectQueryMsg struct { - Owner *struct{} `json:"owner,omitempty"` - Capitalized *Text `json:"capitalized,omitempty"` - Chain *ChainQuery `json:"chain,omitempty"` - SubMsgResult *SubCall `json:"sub_msg_result,omitempty"` -} - -type ChainQuery struct { - Request *wasmvmtypes.QueryRequest `json:"request,omitempty"` -} - -type Text struct { - Text string `json:"text"` -} - -type SubCall struct { - ID uint64 `json:"id"` -} - -type OwnerResponse struct { - Owner string `json:"owner,omitempty"` -} - -type ChainResponse struct { - Data []byte `json:"data,omitempty"` -} - -func buildReflectQuery(t *testing.T, query *ReflectQueryMsg) []byte { +func buildReflectQuery(t *testing.T, query *testdata.ReflectQueryMsg) []byte { bz, err := json.Marshal(query) require.NoError(t, err) return bz @@ -94,9 +47,7 @@ func TestReflectContractSend(t *testing.T) { _, _, bob := keyPubAddr() // upload reflect code - reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - reflectID, err := keeper.Create(ctx, creator, reflectCode, nil) + reflectID, err := keeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) require.NoError(t, err) require.Equal(t, uint64(1), reflectID) @@ -148,8 +99,8 @@ func TestReflectContractSend(t *testing.T) { }, }, }} - reflectSend := ReflectHandleMsg{ - Reflect: &reflectPayload{ + reflectSend := testdata.ReflectHandleMsg{ + Reflect: &testdata.ReflectPayload{ Msgs: msgs, }, } @@ -176,9 +127,7 @@ func TestReflectCustomMsg(t *testing.T) { _, _, fred := keyPubAddr() // upload code - reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - codeID, err := keeper.Create(ctx, creator, reflectCode, nil) + codeID, err := keeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) require.NoError(t, err) require.Equal(t, uint64(1), codeID) @@ -189,8 +138,8 @@ func TestReflectCustomMsg(t *testing.T) { require.NotEmpty(t, contractAddr) // set owner to bob - transfer := ReflectHandleMsg{ - Change: &ownerPayload{ + transfer := testdata.ReflectHandleMsg{ + ChangeOwner: &testdata.OwnerPayload{ Owner: bob, }, } @@ -216,8 +165,8 @@ func TestReflectCustomMsg(t *testing.T) { }, }, }} - reflectSend := ReflectHandleMsg{ - Reflect: &reflectPayload{ + reflectSend := testdata.ReflectHandleMsg{ + Reflect: &testdata.ReflectPayload{ Msgs: msgs, }, } @@ -240,8 +189,8 @@ func TestReflectCustomMsg(t *testing.T) { } opaque, err := toReflectRawMsg(cdc, sdkSendMsg) require.NoError(t, err) - reflectOpaque := ReflectHandleMsg{ - Reflect: &reflectPayload{ + reflectOpaque := testdata.ReflectHandleMsg{ + Reflect: &testdata.ReflectPayload{ Msgs: []wasmvmtypes.CosmosMsg{opaque}, }, } @@ -267,9 +216,7 @@ func TestMaskReflectCustomQuery(t *testing.T) { creator := keepers.Faucet.NewFundedAccount(ctx, deposit...) // upload code - reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - codeID, err := keepers.ContractKeeper.Create(ctx, creator, reflectCode, nil) + codeID, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) require.NoError(t, err) require.Equal(t, uint64(1), codeID) @@ -280,21 +227,21 @@ func TestMaskReflectCustomQuery(t *testing.T) { require.NotEmpty(t, contractAddr) // let's perform a normal query of state - ownerQuery := ReflectQueryMsg{ + ownerQuery := testdata.ReflectQueryMsg{ Owner: &struct{}{}, } ownerQueryBz, err := json.Marshal(ownerQuery) require.NoError(t, err) ownerRes, err := keeper.QuerySmart(ctx, contractAddr, ownerQueryBz) require.NoError(t, err) - var res OwnerResponse + var res testdata.OwnerResponse err = json.Unmarshal(ownerRes, &res) require.NoError(t, err) assert.Equal(t, res.Owner, creator.String()) // and now making use of the custom querier callbacks - customQuery := ReflectQueryMsg{ - Capitalized: &Text{ + customQuery := testdata.ReflectQueryMsg{ + Capitalized: &testdata.Text{ Text: "all Caps noW", }, } @@ -319,9 +266,7 @@ func TestReflectStargateQuery(t *testing.T) { creator := keepers.Faucet.NewFundedAccount(ctx, funds...) // upload code - reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - codeID, err := keepers.ContractKeeper.Create(ctx, creator, reflectCode, nil) + codeID, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) require.NoError(t, err) require.Equal(t, uint64(1), codeID) @@ -338,13 +283,13 @@ func TestReflectStargateQuery(t *testing.T) { }, }, } - simpleQueryBz, err := json.Marshal(ReflectQueryMsg{ - Chain: &ChainQuery{Request: &bankQuery}, + simpleQueryBz, err := json.Marshal(testdata.ReflectQueryMsg{ + Chain: &testdata.ChainQuery{Request: &bankQuery}, }) require.NoError(t, err) simpleRes, err := keeper.QuerySmart(ctx, contractAddr, simpleQueryBz) require.NoError(t, err) - var simpleChain ChainResponse + var simpleChain testdata.ChainResponse mustParse(t, simpleRes, &simpleChain) var simpleBalance wasmvmtypes.AllBalancesResponse mustParse(t, simpleChain.Data, &simpleBalance) @@ -363,9 +308,7 @@ func TestReflectInvalidStargateQuery(t *testing.T) { creator := keepers.Faucet.NewFundedAccount(ctx, funds...) // upload code - reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - codeID, err := keepers.ContractKeeper.Create(ctx, creator, reflectCode, nil) + codeID, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) require.NoError(t, err) require.Equal(t, uint64(1), codeID) @@ -385,8 +328,8 @@ func TestReflectInvalidStargateQuery(t *testing.T) { Data: protoQueryBin, }, } - protoQueryBz, err := json.Marshal(ReflectQueryMsg{ - Chain: &ChainQuery{Request: &protoRequest}, + protoQueryBz, err := json.Marshal(testdata.ReflectQueryMsg{ + Chain: &testdata.ChainQuery{Request: &protoRequest}, }) require.NoError(t, err) @@ -402,8 +345,8 @@ func TestReflectInvalidStargateQuery(t *testing.T) { Data: []byte{}, }, } - protoQueryBz, err = json.Marshal(ReflectQueryMsg{ - Chain: &ChainQuery{Request: &protoRequest}, + protoQueryBz, err = json.Marshal(testdata.ReflectQueryMsg{ + Chain: &testdata.ChainQuery{Request: &protoRequest}, }) require.NoError(t, err) @@ -419,8 +362,8 @@ func TestReflectInvalidStargateQuery(t *testing.T) { Data: []byte{}, }, } - protoQueryBz, err = json.Marshal(ReflectQueryMsg{ - Chain: &ChainQuery{Request: &protoRequest}, + protoQueryBz, err = json.Marshal(testdata.ReflectQueryMsg{ + Chain: &testdata.ChainQuery{Request: &protoRequest}, }) require.NoError(t, err) @@ -443,9 +386,7 @@ func TestMaskReflectWasmQueries(t *testing.T) { creator := keepers.Faucet.NewFundedAccount(ctx, deposit...) // upload reflect code - reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - reflectID, err := keepers.ContractKeeper.Create(ctx, creator, reflectCode, nil) + reflectID, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) require.NoError(t, err) require.Equal(t, uint64(1), reflectID) @@ -456,10 +397,10 @@ func TestMaskReflectWasmQueries(t *testing.T) { require.NotEmpty(t, reflectAddr) // for control, let's make some queries directly on the reflect - ownerQuery := buildReflectQuery(t, &ReflectQueryMsg{Owner: &struct{}{}}) + ownerQuery := buildReflectQuery(t, &testdata.ReflectQueryMsg{Owner: &struct{}{}}) res, err := keeper.QuerySmart(ctx, reflectAddr, ownerQuery) require.NoError(t, err) - var ownerRes OwnerResponse + var ownerRes testdata.OwnerResponse mustParse(t, res, &ownerRes) require.Equal(t, ownerRes.Owner, creator.String()) @@ -471,7 +412,7 @@ func TestMaskReflectWasmQueries(t *testing.T) { require.Equal(t, stateRes.Owner, creator.String()) // now, let's reflect a smart query into the x/wasm handlers and see if we get the same result - reflectOwnerQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ + reflectOwnerQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ Smart: &wasmvmtypes.SmartQuery{ ContractAddr: reflectAddr.String(), Msg: ownerQuery, @@ -481,14 +422,14 @@ func TestMaskReflectWasmQueries(t *testing.T) { res, err = keeper.QuerySmart(ctx, reflectAddr, reflectOwnerBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response - var reflectRes ChainResponse + var reflectRes testdata.ChainResponse mustParse(t, res, &reflectRes) - var reflectOwnerRes OwnerResponse + var reflectOwnerRes testdata.OwnerResponse mustParse(t, reflectRes.Data, &reflectOwnerRes) require.Equal(t, reflectOwnerRes.Owner, creator.String()) // and with queryRaw - reflectStateQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ + reflectStateQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ Raw: &wasmvmtypes.RawQuery{ ContractAddr: reflectAddr.String(), Key: configKey, @@ -498,7 +439,7 @@ func TestMaskReflectWasmQueries(t *testing.T) { res, err = keeper.QuerySmart(ctx, reflectAddr, reflectStateBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response - var reflectRawRes ChainResponse + var reflectRawRes testdata.ChainResponse mustParse(t, res, &reflectRawRes) // now, with the raw data, we can parse it into state var reflectStateRes reflectState @@ -515,9 +456,7 @@ func TestWasmRawQueryWithNil(t *testing.T) { creator := keepers.Faucet.NewFundedAccount(ctx, deposit...) // upload reflect code - reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - reflectID, err := keepers.ContractKeeper.Create(ctx, creator, reflectCode, nil) + reflectID, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) require.NoError(t, err) require.Equal(t, uint64(1), reflectID) @@ -533,7 +472,7 @@ func TestWasmRawQueryWithNil(t *testing.T) { require.Nil(t, raw) // and with queryRaw - reflectQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ + reflectQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ Raw: &wasmvmtypes.RawQuery{ ContractAddr: reflectAddr.String(), Key: missingKey, @@ -544,7 +483,7 @@ func TestWasmRawQueryWithNil(t *testing.T) { require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response - var reflectRawRes ChainResponse + var reflectRawRes testdata.ChainResponse mustParse(t, res, &reflectRawRes) // and make sure there is no data require.Empty(t, reflectRawRes.Data) @@ -629,8 +568,8 @@ func fromReflectRawMsg(cdc codec.Codec) CustomEncoder { } type reflectCustomQuery struct { - Ping *struct{} `json:"ping,omitempty"` - Capitalized *Text `json:"capitalized,omitempty"` + Ping *struct{} `json:"ping,omitempty"` + Capitalized *testdata.Text `json:"capitalized,omitempty"` } // this is from the go code back to the contract (capitalized or ping) diff --git a/x/wasm/keeper/staking_test.go b/x/wasm/keeper/staking_test.go index 5265be59..b6d719f1 100644 --- a/x/wasm/keeper/staking_test.go +++ b/x/wasm/keeper/staking_test.go @@ -5,8 +5,6 @@ import ( "io/ioutil" "testing" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" @@ -21,6 +19,9 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/CosmWasm/wasmd/x/wasm/keeper/testdata" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" ) type StakingInitMsg struct { @@ -35,12 +36,12 @@ type StakingInitMsg struct { // StakingHandleMsg is used to encode handle messages type StakingHandleMsg struct { - Transfer *transferPayload `json:"transfer,omitempty"` - Bond *struct{} `json:"bond,omitempty"` - Unbond *unbondPayload `json:"unbond,omitempty"` - Claim *struct{} `json:"claim,omitempty"` - Reinvest *struct{} `json:"reinvest,omitempty"` - Change *ownerPayload `json:"change_owner,omitempty"` + Transfer *transferPayload `json:"transfer,omitempty"` + Bond *struct{} `json:"bond,omitempty"` + Unbond *unbondPayload `json:"unbond,omitempty"` + Claim *struct{} `json:"claim,omitempty"` + Reinvest *struct{} `json:"reinvest,omitempty"` + Change *testdata.OwnerPayload `json:"change_owner,omitempty"` } type transferPayload struct { @@ -446,9 +447,7 @@ func TestQueryStakingInfo(t *testing.T) { creator := initInfo.faucet.NewFundedAccount(ctx, deposit...) // upload mask code - maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - maskID, err := initInfo.contractKeeper.Create(ctx, creator, maskCode, nil) + maskID, err := initInfo.contractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) require.NoError(t, err) require.Equal(t, uint64(2), maskID) @@ -459,21 +458,21 @@ func TestQueryStakingInfo(t *testing.T) { // STEP 3: now, let's reflect some queries. // let's get the bonded denom - reflectBondedQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ + reflectBondedQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ BondedDenom: &struct{}{}, }}}} reflectBondedBin := buildReflectQuery(t, &reflectBondedQuery) res, err := keeper.QuerySmart(ctx, maskAddr, reflectBondedBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response - var reflectRes ChainResponse + var reflectRes testdata.ChainResponse mustParse(t, res, &reflectRes) var bondedRes wasmvmtypes.BondedDenomResponse mustParse(t, reflectRes.Data, &bondedRes) assert.Equal(t, "stake", bondedRes.Denom) // now, let's reflect a smart query into the x/wasm handlers and see if we get the same result - reflectAllValidatorsQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ + reflectAllValidatorsQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ AllValidators: &wasmvmtypes.AllValidatorsQuery{}, }}}} reflectAllValidatorsBin := buildReflectQuery(t, &reflectAllValidatorsQuery) @@ -492,7 +491,7 @@ func TestQueryStakingInfo(t *testing.T) { require.Contains(t, valInfo.MaxChangeRate, "0.010") // find a validator - reflectValidatorQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ + reflectValidatorQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ Validator: &wasmvmtypes.ValidatorQuery{ Address: valAddr.String(), }, @@ -514,7 +513,7 @@ func TestQueryStakingInfo(t *testing.T) { // missing validator noVal := sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()) - reflectNoValidatorQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ + reflectNoValidatorQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ Validator: &wasmvmtypes.ValidatorQuery{ Address: noVal.String(), }, @@ -529,7 +528,7 @@ func TestQueryStakingInfo(t *testing.T) { require.Nil(t, noValidatorRes.Validator) // test to get all my delegations - reflectAllDelegationsQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ + reflectAllDelegationsQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ AllDelegations: &wasmvmtypes.AllDelegationsQuery{ Delegator: contractAddr.String(), }, @@ -552,7 +551,7 @@ func TestQueryStakingInfo(t *testing.T) { require.Equal(t, funds[0].Amount.String(), delInfo.Amount.Amount) // test to get one delegations - reflectDelegationQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ + reflectDelegationQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ Delegation: &wasmvmtypes.DelegationQuery{ Validator: valAddr.String(), Delegator: contractAddr.String(), diff --git a/x/wasm/keeper/submsg_test.go b/x/wasm/keeper/submsg_test.go index 7d83c8ff..9d1904fa 100644 --- a/x/wasm/keeper/submsg_test.go +++ b/x/wasm/keeper/submsg_test.go @@ -7,13 +7,13 @@ import ( "strconv" "testing" - "github.com/CosmWasm/wasmd/x/wasm/types" - - "github.com/stretchr/testify/assert" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/CosmWasm/wasmd/x/wasm/keeper/testdata" + "github.com/CosmWasm/wasmd/x/wasm/types" ) // test handing of submessages, very closely related to the reflect_test @@ -31,9 +31,7 @@ func TestDispatchSubMsgSuccessCase(t *testing.T) { _, _, fred := keyPubAddr() // upload code - reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - codeID, err := keepers.ContractKeeper.Create(ctx, creator, reflectCode, nil) + codeID, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) require.NoError(t, err) require.Equal(t, uint64(1), codeID) @@ -59,8 +57,8 @@ func TestDispatchSubMsgSuccessCase(t *testing.T) { }, }, } - reflectSend := ReflectHandleMsg{ - ReflectSubMsg: &reflectSubPayload{ + reflectSend := testdata.ReflectHandleMsg{ + ReflectSubMsg: &testdata.ReflectSubPayload{ Msgs: []wasmvmtypes.SubMsg{{ ID: 7, Msg: msg, @@ -80,8 +78,8 @@ func TestDispatchSubMsgSuccessCase(t *testing.T) { checkAccount(t, ctx, accKeeper, bankKeeper, creator, creatorBalance) // query the reflect state to ensure the result was stored - query := ReflectQueryMsg{ - SubMsgResult: &SubCall{ID: 7}, + query := testdata.ReflectQueryMsg{ + SubMsgResult: &testdata.SubCall{ID: 7}, } queryBz, err := json.Marshal(query) require.NoError(t, err) @@ -122,9 +120,7 @@ func TestDispatchSubMsgErrorHandling(t *testing.T) { uploader := keepers.Faucet.NewFundedAccount(ctx, contractStart.Add(contractStart...)...) // upload code - reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - reflectID, err := keepers.ContractKeeper.Create(ctx, uploader, reflectCode, nil) + reflectID, err := keepers.ContractKeeper.Create(ctx, uploader, testdata.ReflectContractWasm(), nil) require.NoError(t, err) // create hackatom contract for testing (for infinite loop) @@ -300,8 +296,8 @@ func TestDispatchSubMsgErrorHandling(t *testing.T) { require.NoError(t, err) msg := tc.msg(contractAddr.String(), empty.String()) - reflectSend := ReflectHandleMsg{ - ReflectSubMsg: &reflectSubPayload{ + reflectSend := testdata.ReflectHandleMsg{ + ReflectSubMsg: &testdata.ReflectSubPayload{ Msgs: []wasmvmtypes.SubMsg{{ ID: tc.submsgID, Msg: msg, @@ -331,8 +327,8 @@ func TestDispatchSubMsgErrorHandling(t *testing.T) { require.NoError(t, err) // query the reply - query := ReflectQueryMsg{ - SubMsgResult: &SubCall{ID: tc.submsgID}, + query := testdata.ReflectQueryMsg{ + SubMsgResult: &testdata.SubCall{ID: tc.submsgID}, } queryBz, err := json.Marshal(query) require.NoError(t, err) @@ -381,9 +377,7 @@ func TestDispatchSubMsgEncodeToNoSdkMsg(t *testing.T) { _, _, fred := keyPubAddr() // upload code - reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - codeID, err := keepers.ContractKeeper.Create(ctx, creator, reflectCode, nil) + codeID, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) require.NoError(t, err) // creator instantiates a contract and gives it tokens @@ -403,8 +397,8 @@ func TestDispatchSubMsgEncodeToNoSdkMsg(t *testing.T) { }, }, } - reflectSend := ReflectHandleMsg{ - ReflectSubMsg: &reflectSubPayload{ + reflectSend := testdata.ReflectHandleMsg{ + ReflectSubMsg: &testdata.ReflectSubPayload{ Msgs: []wasmvmtypes.SubMsg{{ ID: 7, Msg: msg, @@ -418,8 +412,8 @@ func TestDispatchSubMsgEncodeToNoSdkMsg(t *testing.T) { require.NoError(t, err) // query the reflect state to ensure the result was stored - query := ReflectQueryMsg{ - SubMsgResult: &SubCall{ID: 7}, + query := testdata.ReflectQueryMsg{ + SubMsgResult: &testdata.SubCall{ID: 7}, } queryBz, err := json.Marshal(query) require.NoError(t, err) @@ -449,9 +443,7 @@ func TestDispatchSubMsgConditionalReplyOn(t *testing.T) { _, _, fred := keyPubAddr() // upload code - reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - codeID, err := keepers.ContractKeeper.Create(ctx, creator, reflectCode, nil) + codeID, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) require.NoError(t, err) // creator instantiates a contract and gives it tokens @@ -529,8 +521,8 @@ func TestDispatchSubMsgConditionalReplyOn(t *testing.T) { subMsg.ReplyOn = wasmvmtypes.ReplyError } - reflectSend := ReflectHandleMsg{ - ReflectSubMsg: &reflectSubPayload{ + reflectSend := testdata.ReflectHandleMsg{ + ReflectSubMsg: &testdata.ReflectSubPayload{ Msgs: []wasmvmtypes.SubMsg{subMsg}, }, } @@ -545,8 +537,8 @@ func TestDispatchSubMsgConditionalReplyOn(t *testing.T) { } // query the reflect state to check if the result was stored - query := ReflectQueryMsg{ - SubMsgResult: &SubCall{ID: id}, + query := testdata.ReflectQueryMsg{ + SubMsgResult: &testdata.SubCall{ID: id}, } queryBz, err := json.Marshal(query) require.NoError(t, err) diff --git a/x/wasm/keeper/test_common.go b/x/wasm/keeper/test_common.go index 29e47ab4..71722896 100644 --- a/x/wasm/keeper/test_common.go +++ b/x/wasm/keeper/test_common.go @@ -8,6 +8,8 @@ import ( "testing" "time" + "github.com/CosmWasm/wasmd/x/wasm/keeper/testdata" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/std" @@ -72,7 +74,7 @@ import ( "github.com/CosmWasm/wasmd/x/wasm/types" ) -var ModuleBasics = module.NewBasicManager( +var moduleBasics = module.NewBasicManager( auth.AppModuleBasic{}, bank.AppModuleBasic{}, capability.AppModuleBasic{}, @@ -103,8 +105,8 @@ func MakeEncodingConfig(_ testing.TB) wasmappparams.EncodingConfig { std.RegisterInterfaces(interfaceRegistry) std.RegisterLegacyAminoCodec(amino) - ModuleBasics.RegisterLegacyAminoCodec(amino) - ModuleBasics.RegisterInterfaces(interfaceRegistry) + moduleBasics.RegisterLegacyAminoCodec(amino) + moduleBasics.RegisterInterfaces(interfaceRegistry) // add wasmd types types.RegisterInterfaces(interfaceRegistry) types.RegisterLegacyAminoCodec(amino) @@ -545,11 +547,8 @@ func StoreIBCReflectContract(t testing.TB, ctx sdk.Context, keepers TestKeepers) } func StoreReflectContract(t testing.TB, ctx sdk.Context, keepers TestKeepers) uint64 { - wasmCode, err := ioutil.ReadFile("./testdata/reflect.wasm") - require.NoError(t, err) - _, _, creatorAddr := keyPubAddr() - codeID, err := keepers.ContractKeeper.Create(ctx, creatorAddr, wasmCode, nil) + codeID, err := keepers.ContractKeeper.Create(ctx, creatorAddr, testdata.ReflectContractWasm(), nil) require.NoError(t, err) return codeID } diff --git a/x/wasm/keeper/testdata/reflect.go b/x/wasm/keeper/testdata/reflect.go new file mode 100644 index 00000000..51f5f14d --- /dev/null +++ b/x/wasm/keeper/testdata/reflect.go @@ -0,0 +1,62 @@ +package testdata + +import ( + _ "embed" + + typwasmvmtypes "github.com/CosmWasm/wasmvm/types" + "github.com/cosmos/cosmos-sdk/types" +) + +//go:embed reflect.wasm +var reflectContract []byte + +func ReflectContractWasm() []byte { + return reflectContract +} + +// ReflectHandleMsg is used to encode handle messages +type ReflectHandleMsg struct { + Reflect *ReflectPayload `json:"reflect_msg,omitempty"` + ReflectSubMsg *ReflectSubPayload `json:"reflect_sub_msg,omitempty"` + ChangeOwner *OwnerPayload `json:"change_owner,omitempty"` +} + +type OwnerPayload struct { + Owner types.Address `json:"owner"` +} + +type ReflectPayload struct { + Msgs []typwasmvmtypes.CosmosMsg `json:"msgs"` +} + +type ReflectSubPayload struct { + Msgs []typwasmvmtypes.SubMsg `json:"msgs"` +} + +// ReflectQueryMsg is used to encode query messages +type ReflectQueryMsg struct { + Owner *struct{} `json:"owner,omitempty"` + Capitalized *Text `json:"capitalized,omitempty"` + Chain *ChainQuery `json:"chain,omitempty"` + SubMsgResult *SubCall `json:"sub_msg_result,omitempty"` +} + +type ChainQuery struct { + Request *typwasmvmtypes.QueryRequest `json:"request,omitempty"` +} + +type Text struct { + Text string `json:"text"` +} + +type SubCall struct { + ID uint64 `json:"id"` +} + +type OwnerResponse struct { + Owner string `json:"owner,omitempty"` +} + +type ChainResponse struct { + Data []byte `json:"data,omitempty"` +} diff --git a/x/wasm/module.go b/x/wasm/module.go index 9f078bb1..a237a910 100644 --- a/x/wasm/module.go +++ b/x/wasm/module.go @@ -13,7 +13,6 @@ 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" @@ -105,7 +104,7 @@ type AppModule struct { keeper *Keeper validatorSetSource keeper.ValidatorSetSource accountKeeper types.AccountKeeper // for simulation - bankKeeper simKeeper.BankKeeper + bankKeeper simulation.BankKeeper } // ConsensusVersion is a sequence number for state-breaking change of the @@ -120,7 +119,7 @@ func NewAppModule( keeper *Keeper, validatorSetSource keeper.ValidatorSetSource, ak types.AccountKeeper, - bk simKeeper.BankKeeper, + bk simulation.BankKeeper, ) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, diff --git a/x/wasm/module_test.go b/x/wasm/module_test.go index 5c68cb2c..ab52363b 100644 --- a/x/wasm/module_test.go +++ b/x/wasm/module_test.go @@ -6,6 +6,8 @@ import ( "io/ioutil" "testing" + "github.com/CosmWasm/wasmd/x/wasm/keeper/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" @@ -66,7 +68,7 @@ var ( _, _, addrAcc1 = keyPubAddr() addr1 = addrAcc1.String() testContract = mustLoad("./keeper/testdata/hackatom.wasm") - maskContract = mustLoad("./keeper/testdata/reflect.wasm") + maskContract = testdata.ReflectContractWasm() oldContract = mustLoad("./testdata/escrow_0.7.wasm") ) diff --git a/x/wasm/simulation/genesis.go b/x/wasm/simulation/genesis.go index c9bd60e7..039a4394 100644 --- a/x/wasm/simulation/genesis.go +++ b/x/wasm/simulation/genesis.go @@ -8,7 +8,7 @@ import ( // RandomizeGenState generates a random GenesisState for wasm func RandomizedGenState(simstate *module.SimulationState) { - params := RandomParams(simstate.Rand) + params := types.DefaultParams() wasmGenesis := types.GenesisState{ Params: params, Codes: nil, diff --git a/x/wasm/simulation/operations.go b/x/wasm/simulation/operations.go index 40de06a5..66635a2b 100644 --- a/x/wasm/simulation/operations.go +++ b/x/wasm/simulation/operations.go @@ -1,17 +1,22 @@ package simulation import ( + "encoding/json" "io/ioutil" "math/rand" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" "github.com/cosmos/cosmos-sdk/baseapp" simappparams "github.com/cosmos/cosmos-sdk/simapp/params" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "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" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + "github.com/CosmWasm/wasmd/x/wasm/keeper/testdata" "github.com/CosmWasm/wasmd/x/wasm/types" ) @@ -20,6 +25,7 @@ import ( const ( OpWeightMsgStoreCode = "op_weight_msg_store_code" OpWeightMsgInstantiateContract = "op_weight_msg_instantiate_contract" + OpWeightMsgExecuteContract = "op_weight_msg_execute_contract" OpReflectContractPath = "op_reflect_contract_path" ) @@ -27,18 +33,26 @@ const ( type WasmKeeper interface { GetParams(ctx sdk.Context) types.Params IterateCodeInfos(ctx sdk.Context, cb func(uint64, types.CodeInfo) bool) + IterateContractInfo(ctx sdk.Context, cb func(sdk.AccAddress, types.ContractInfo) bool) + QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) + PeekAutoIncrementID(ctx sdk.Context, lastIDKey []byte) uint64 +} +type BankKeeper interface { + simulation.BankKeeper + IsSendEnabledCoin(ctx sdk.Context, coin sdk.Coin) bool } // WeightedOperations returns all the operations from the module with their respective weights func WeightedOperations( simstate *module.SimulationState, ak types.AccountKeeper, - bk simulation.BankKeeper, + bk BankKeeper, wasmKeeper WasmKeeper, ) simulation.WeightedOperations { var ( weightMsgStoreCode int weightMsgInstantiateContract int + weightMsgExecuteContract int wasmContractPath string ) @@ -53,32 +67,53 @@ func WeightedOperations( weightMsgInstantiateContract = params.DefaultWeightMsgInstantiateContract }, ) + simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgExecuteContract, &weightMsgInstantiateContract, nil, + func(_ *rand.Rand) { + weightMsgExecuteContract = params.DefaultWeightMsgExecuteContract + }, + ) simstate.AppParams.GetOrGenerate(simstate.Cdc, OpReflectContractPath, &wasmContractPath, nil, func(_ *rand.Rand) { - // simulations are run from the `app` folder - wasmContractPath = "../x/wasm/keeper/testdata/reflect.wasm" + wasmContractPath = "" }, ) - wasmBz, err := ioutil.ReadFile(wasmContractPath) - if err != nil { - panic(err) + var wasmBz []byte + if wasmContractPath == "" { + wasmBz = testdata.ReflectContractWasm() + } else { + var err error + wasmBz, err = ioutil.ReadFile(wasmContractPath) + if err != nil { + panic(err) + } } return simulation.WeightedOperations{ simulation.NewWeightedOperation( weightMsgStoreCode, - SimulateMsgStoreCode(ak, bk, wasmKeeper, wasmBz), + SimulateMsgStoreCode(ak, bk, wasmKeeper, wasmBz, 5_000_000), ), simulation.NewWeightedOperation( weightMsgInstantiateContract, - SimulateMsgInstantiateContract(ak, bk, wasmKeeper), + SimulateMsgInstantiateContract(ak, bk, wasmKeeper, DefaultSimulationCodeIDSelector), + ), + simulation.NewWeightedOperation( + weightMsgExecuteContract, + SimulateMsgExecuteContract( + ak, + bk, + wasmKeeper, + DefaultSimulationExecuteContractSelector, + DefaultSimulationExecuteSenderSelector, + DefaultSimulationExecutePayloader, + ), ), } } // SimulateMsgStoreCode generates a MsgStoreCode with random values -func SimulateMsgStoreCode(ak types.AccountKeeper, bk simulation.BankKeeper, wasmKeeper WasmKeeper, wasmBz []byte) simtypes.Operation { +func SimulateMsgStoreCode(ak types.AccountKeeper, bk simulation.BankKeeper, wasmKeeper WasmKeeper, wasmBz []byte, gas uint64) simtypes.Operation { return func( r *rand.Rand, app *baseapp.BaseApp, @@ -90,15 +125,15 @@ func SimulateMsgStoreCode(ak types.AccountKeeper, bk simulation.BankKeeper, wasm return simtypes.NoOpMsg(types.ModuleName, types.MsgStoreCode{}.Type(), "no chain permission"), nil, nil } - config := &types.AccessConfig{ - Permission: types.AccessTypeEverybody, - } - simAccount, _ := simtypes.RandomAcc(r, accs) + + permission := wasmKeeper.GetParams(ctx).InstantiateDefaultPermission + config := permission.With(simAccount.Address) + msg := types.MsgStoreCode{ Sender: simAccount.Address.String(), WASMByteCode: wasmBz, - InstantiatePermission: config, + InstantiatePermission: &config, } txCtx := simulation.OperationInput{ @@ -115,12 +150,28 @@ func SimulateMsgStoreCode(ak types.AccountKeeper, bk simulation.BankKeeper, wasm ModuleName: types.ModuleName, } - return simulation.GenAndDeliverTxWithRandFees(txCtx) + return GenAndDeliverTxWithRandFees(txCtx, gas) } } +// CodeIDSelector returns code id to be used in simulations +type CodeIDSelector = func(ctx sdk.Context, wasmKeeper WasmKeeper) uint64 + +// DefaultSimulationCodeIDSelector picks the first code id +func DefaultSimulationCodeIDSelector(ctx sdk.Context, wasmKeeper WasmKeeper) uint64 { + 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 + }) + return codeID +} + // SimulateMsgInstantiateContract generates a MsgInstantiateContract with random values -func SimulateMsgInstantiateContract(ak types.AccountKeeper, bk simulation.BankKeeper, wasmKeeper WasmKeeper) simtypes.Operation { +func SimulateMsgInstantiateContract(ak types.AccountKeeper, bk BankKeeper, wasmKeeper WasmKeeper, codeSelector CodeIDSelector) simtypes.Operation { return func( r *rand.Rand, app *baseapp.BaseApp, @@ -130,20 +181,17 @@ func SimulateMsgInstantiateContract(ak types.AccountKeeper, bk simulation.BankKe ) (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 - }) - + codeID := codeSelector(ctx, wasmKeeper) if codeID == 0 { return simtypes.NoOpMsg(types.ModuleName, types.MsgInstantiateContract{}.Type(), "no codes with permission available"), nil, nil } - - spendable := bk.SpendableCoins(ctx, simAccount.Address) + deposit := sdk.Coins{} + spendableCoins := bk.SpendableCoins(ctx, simAccount.Address) + for _, v := range spendableCoins { + if bk.IsSendEnabledCoin(ctx, v) { + deposit = deposit.Add(simtypes.RandSubsetCoins(r, sdk.NewCoins(v))...) + } + } msg := types.MsgInstantiateContract{ Sender: simAccount.Address.String(), @@ -151,23 +199,154 @@ func SimulateMsgInstantiateContract(ak types.AccountKeeper, bk simulation.BankKe CodeID: codeID, Label: simtypes.RandStringOfLength(r, 10), Msg: []byte(`{}`), - Funds: simtypes.RandSubsetCoins(r, spendable), + Funds: deposit, } 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, + 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, + CoinsSpentInMsg: deposit, } return simulation.GenAndDeliverTxWithRandFees(txCtx) } } + +// MsgExecuteContractSelector returns contract address to be used in simulations +type MsgExecuteContractSelector = func(ctx sdk.Context, wasmKeeper WasmKeeper) sdk.AccAddress + +// MsgExecutePayloader extension point to modify msg with custom payload +type MsgExecutePayloader func(msg *types.MsgExecuteContract) error + +// MsgExecuteSenderSelector extension point that returns the sender address +type MsgExecuteSenderSelector func(wasmKeeper WasmKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, accs []simtypes.Account) (simtypes.Account, error) + +// SimulateMsgExecuteContract create a execute message a reflect contract instance +func SimulateMsgExecuteContract( + ak types.AccountKeeper, + bk BankKeeper, + wasmKeeper WasmKeeper, + contractSelector MsgExecuteContractSelector, + senderSelector MsgExecuteSenderSelector, + payloader MsgExecutePayloader, +) simtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + contractAddr := contractSelector(ctx, wasmKeeper) + if contractAddr == nil { + return simtypes.NoOpMsg(types.ModuleName, types.MsgExecuteContract{}.Type(), "no contract instance available"), nil, nil + } + simAccount, err := senderSelector(wasmKeeper, ctx, contractAddr, accs) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.MsgExecuteContract{}.Type(), "query contract owner"), nil, err + } + + deposit := sdk.Coins{} + spendableCoins := bk.SpendableCoins(ctx, simAccount.Address) + for _, v := range spendableCoins { + if bk.IsSendEnabledCoin(ctx, v) { + deposit = deposit.Add(simtypes.RandSubsetCoins(r, sdk.NewCoins(v))...) + } + } + if deposit.IsZero() { + return simtypes.NoOpMsg(types.ModuleName, types.MsgExecuteContract{}.Type(), "broke account"), nil, nil + } + msg := types.MsgExecuteContract{ + Sender: simAccount.Address.String(), + Contract: contractAddr.String(), + Funds: deposit, + } + if err := payloader(&msg); err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.MsgExecuteContract{}.Type(), "contract execute payload"), nil, err + } + + 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, + CoinsSpentInMsg: deposit, + } + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// DefaultSimulationExecuteContractSelector picks the first contract address +func DefaultSimulationExecuteContractSelector(ctx sdk.Context, wasmKeeper WasmKeeper) sdk.AccAddress { + var r sdk.AccAddress + wasmKeeper.IterateContractInfo(ctx, func(address sdk.AccAddress, info types.ContractInfo) bool { + r = address + return true + }) + return r +} + +// DefaultSimulationExecuteSenderSelector queries reflect contract for owner address and selects accounts +func DefaultSimulationExecuteSenderSelector(wasmKeeper WasmKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, accs []simtypes.Account) (simtypes.Account, error) { + var none simtypes.Account + bz, err := json.Marshal(testdata.ReflectQueryMsg{Owner: &struct{}{}}) + if err != nil { + return none, sdkerrors.Wrap(err, "build smart query") + } + got, err := wasmKeeper.QuerySmart(ctx, contractAddr, bz) + if err != nil { + return none, sdkerrors.Wrap(err, "exec smart query") + } + var ownerRes testdata.OwnerResponse + if err := json.Unmarshal(got, &ownerRes); err != nil || ownerRes.Owner == "" { + return none, sdkerrors.Wrap(err, "parse smart query response") + } + ownerAddr, err := sdk.AccAddressFromBech32(ownerRes.Owner) + if err != nil { + return none, sdkerrors.Wrap(err, "parse contract owner address") + } + simAccount, ok := simtypes.FindAccount(accs, ownerAddr) + if !ok { + return none, sdkerrors.Wrap(err, "unknown contract owner address") + } + return simAccount, nil +} + +// DefaultSimulationExecutePayloader implements a bank msg to send the +// tokens from contract account back to original sender +func DefaultSimulationExecutePayloader(msg *types.MsgExecuteContract) error { + reflectSend := testdata.ReflectHandleMsg{ + Reflect: &testdata.ReflectPayload{ + Msgs: []wasmvmtypes.CosmosMsg{{ + Bank: &wasmvmtypes.BankMsg{ + Send: &wasmvmtypes.SendMsg{ + ToAddress: msg.Sender, // + Amount: wasmkeeper.ConvertSdkCoinsToWasmCoins(msg.Funds), + }, + }, + }}, + }, + } + reflectSendBz, err := json.Marshal(reflectSend) + if err != nil { + return err + } + msg.Msg = reflectSendBz + return nil +} diff --git a/x/wasm/simulation/params.go b/x/wasm/simulation/params.go index 0de0d0b4..a6d55b15 100644 --- a/x/wasm/simulation/params.go +++ b/x/wasm/simulation/params.go @@ -12,7 +12,7 @@ import ( ) func ParamChanges(r *rand.Rand, cdc codec.Codec) []simtypes.ParamChange { - params := RandomParams(r) + params := types.DefaultParams() return []simtypes.ParamChange{ simulation.NewSimParamChange(types.ModuleName, string(types.ParamStoreKeyUploadAccess), func(r *rand.Rand) string { @@ -30,13 +30,3 @@ func ParamChanges(r *rand.Rand, cdc codec.Codec) []simtypes.ParamChange { ), } } - -func RandomParams(r *rand.Rand) types.Params { - permissionType := types.AccessType(simtypes.RandIntBetween(r, 1, 3)) - account, _ := simtypes.RandomAcc(r, simtypes.RandomAccounts(r, 10)) - accessConfig := permissionType.With(account.Address) - return types.Params{ - CodeUploadAccess: accessConfig, - InstantiateDefaultPermission: accessConfig.Permission, - } -} diff --git a/x/wasm/simulation/sim_utils.go b/x/wasm/simulation/sim_utils.go new file mode 100644 index 00000000..4f9a00b0 --- /dev/null +++ b/x/wasm/simulation/sim_utils.go @@ -0,0 +1,53 @@ +package simulation + +import ( + "github.com/cosmos/cosmos-sdk/simapp/helpers" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +// GenAndDeliverTxWithRandFees generates a transaction with a random fee and delivers it. +func GenAndDeliverTxWithRandFees(txCtx simulation.OperationInput, gas uint64) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + account := txCtx.AccountKeeper.GetAccount(txCtx.Context, txCtx.SimAccount.Address) + spendable := txCtx.Bankkeeper.SpendableCoins(txCtx.Context, account.GetAddress()) + + var fees sdk.Coins + var err error + + coins, hasNeg := spendable.SafeSub(txCtx.CoinsSpentInMsg) + if hasNeg { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "message doesn't leave room for fees"), nil, err + } + + fees, err = simtypes.RandomFees(txCtx.R, txCtx.Context, coins) + if err != nil { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate fees"), nil, err + } + return GenAndDeliverTx(txCtx, fees, gas) +} + +// GenAndDeliverTx generates a transactions and delivers it. +func GenAndDeliverTx(txCtx simulation.OperationInput, fees sdk.Coins, gas uint64) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + account := txCtx.AccountKeeper.GetAccount(txCtx.Context, txCtx.SimAccount.Address) + tx, err := helpers.GenTx( + txCtx.TxGen, + []sdk.Msg{txCtx.Msg}, + fees, + gas, + txCtx.Context.ChainID(), + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + txCtx.SimAccount.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate mock tx"), nil, err + } + + _, _, err = txCtx.App.Deliver(txCtx.TxGen.TxEncoder(), tx) + if err != nil { + return simtypes.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to deliver tx"), nil, err + } + + return simtypes.NewOperationMsg(txCtx.Msg, true, "", txCtx.Cdc), nil, nil +}