wasmd/x/wasm/client/cli/genesis_msg_test.go

730 lines
21 KiB
Go

package cli
import (
"context"
"encoding/json"
"io/ioutil"
"os"
"path"
"testing"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/CosmWasm/wasmd/x/wasm/keeper"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
var wasmIdent = []byte("\x00\x61\x73\x6D")
var myWellFundedAccount = keeper.RandomBech32AccountAddress(nil)
const defaultTestKeyName = "my-key-name"
func TestGenesisStoreCodeCmd(t *testing.T) {
minimalWasmGenesis := types.GenesisState{
Params: types.DefaultParams(),
}
anyValidWasmFile, err := ioutil.TempFile(t.TempDir(), "wasm")
require.NoError(t, err)
anyValidWasmFile.Write(wasmIdent)
require.NoError(t, anyValidWasmFile.Close())
specs := map[string]struct {
srcGenesis types.GenesisState
mutator func(cmd *cobra.Command)
expError bool
}{
"all good with actor address": {
srcGenesis: minimalWasmGenesis,
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{anyValidWasmFile.Name()})
flagSet := cmd.Flags()
flagSet.Set("run-as", keeper.RandomBech32AccountAddress(t))
},
},
"all good with key name": {
srcGenesis: minimalWasmGenesis,
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{anyValidWasmFile.Name()})
flagSet := cmd.Flags()
flagSet.Set("run-as", defaultTestKeyName)
},
},
"with unknown actor key name should fail": {
srcGenesis: minimalWasmGenesis,
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{anyValidWasmFile.Name()})
flagSet := cmd.Flags()
flagSet.Set("run-as", "unknown key")
},
expError: true,
},
"without actor should fail": {
srcGenesis: minimalWasmGenesis,
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{anyValidWasmFile.Name()})
},
expError: true,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
homeDir := setupGenesis(t, spec.srcGenesis)
// when
cmd := GenesisStoreCodeCmd(homeDir, NewDefaultGenesisIO())
spec.mutator(cmd)
err := executeCmdWithContext(t, homeDir, cmd)
if spec.expError {
require.Error(t, err)
return
}
require.NoError(t, err)
// then
moduleState := loadModuleState(t, homeDir)
assert.Len(t, moduleState.GenMsgs, 1)
})
}
}
func TestInstantiateContractCmd(t *testing.T) {
minimalWasmGenesis := types.GenesisState{
Params: types.DefaultParams(),
}
anyValidWasmFile, err := ioutil.TempFile(t.TempDir(), "wasm")
require.NoError(t, err)
anyValidWasmFile.Write(wasmIdent)
require.NoError(t, anyValidWasmFile.Close())
specs := map[string]struct {
srcGenesis types.GenesisState
mutator func(cmd *cobra.Command)
expMsgCount int
expError bool
}{
"all good with code id in genesis codes": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfo{
CodeHash: []byte("a-valid-code-hash"),
Creator: keeper.RandomBech32AccountAddress(t),
InstantiateConfig: types.AccessConfig{
Permission: types.AccessTypeEverybody,
},
},
CodeBytes: wasmIdent,
},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{"1", `{}`})
flagSet := cmd.Flags()
flagSet.Set("label", "testing")
flagSet.Set("run-as", myWellFundedAccount)
flagSet.Set("no-admin", "true")
},
expMsgCount: 1,
},
"all good with code id from genesis store messages without initial sequence": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_StoreCode{StoreCode: types.MsgStoreCodeFixture()}},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{"1", `{}`})
flagSet := cmd.Flags()
flagSet.Set("label", "testing")
flagSet.Set("run-as", myWellFundedAccount)
flagSet.Set("admin", myWellFundedAccount)
},
expMsgCount: 2,
},
"all good with code id from genesis store messages and sequence set": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_StoreCode{StoreCode: types.MsgStoreCodeFixture()}},
},
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 100},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{"100", `{}`})
flagSet := cmd.Flags()
flagSet.Set("label", "testing")
flagSet.Set("run-as", myWellFundedAccount)
flagSet.Set("no-admin", "true")
},
expMsgCount: 2,
},
"fails with codeID not existing in codes": {
srcGenesis: minimalWasmGenesis,
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{"2", `{}`})
flagSet := cmd.Flags()
flagSet.Set("label", "testing")
flagSet.Set("run-as", myWellFundedAccount)
flagSet.Set("no-admin", "true")
},
expError: true,
},
"fails when instantiation permissions not granted": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_StoreCode{StoreCode: types.MsgStoreCodeFixture(func(code *types.MsgStoreCode) {
code.InstantiatePermission = &types.AllowNobody
})}},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{"1", `{}`})
flagSet := cmd.Flags()
flagSet.Set("label", "testing")
flagSet.Set("run-as", myWellFundedAccount)
flagSet.Set("no-admin", "true")
},
expError: true,
},
"fails if no explicit --no-admin passed": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfo{
CodeHash: []byte("a-valid-code-hash"),
Creator: keeper.RandomBech32AccountAddress(t),
InstantiateConfig: types.AccessConfig{
Permission: types.AccessTypeEverybody,
},
},
CodeBytes: wasmIdent,
},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{"1", `{}`})
flagSet := cmd.Flags()
flagSet.Set("label", "testing")
flagSet.Set("run-as", myWellFundedAccount)
},
expError: true,
},
"fails if both --admin and --no-admin passed": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfo{
CodeHash: []byte("a-valid-code-hash"),
Creator: keeper.RandomBech32AccountAddress(t),
InstantiateConfig: types.AccessConfig{
Permission: types.AccessTypeEverybody,
},
},
CodeBytes: wasmIdent,
},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{"1", `{}`})
flagSet := cmd.Flags()
flagSet.Set("label", "testing")
flagSet.Set("run-as", myWellFundedAccount)
flagSet.Set("no-admin", "true")
flagSet.Set("admin", myWellFundedAccount)
},
expError: true,
},
"succeeds with unknown account when no funds": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfo{
CodeHash: []byte("a-valid-code-hash"),
Creator: keeper.RandomBech32AccountAddress(t),
InstantiateConfig: types.AccessConfig{
Permission: types.AccessTypeEverybody,
},
},
CodeBytes: wasmIdent,
},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{"1", `{}`})
flagSet := cmd.Flags()
flagSet.Set("label", "testing")
flagSet.Set("run-as", keeper.RandomBech32AccountAddress(t))
flagSet.Set("no-admin", "true")
},
expMsgCount: 1,
},
"succeeds with funds from well funded account": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfo{
CodeHash: []byte("a-valid-code-hash"),
Creator: keeper.RandomBech32AccountAddress(t),
InstantiateConfig: types.AccessConfig{
Permission: types.AccessTypeEverybody,
},
},
CodeBytes: wasmIdent,
},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{"1", `{}`})
flagSet := cmd.Flags()
flagSet.Set("label", "testing")
flagSet.Set("run-as", myWellFundedAccount)
flagSet.Set("amount", "100stake")
flagSet.Set("no-admin", "true")
},
expMsgCount: 1,
},
"fails without enough sender balance": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfo{
CodeHash: []byte("a-valid-code-hash"),
Creator: keeper.RandomBech32AccountAddress(t),
InstantiateConfig: types.AccessConfig{
Permission: types.AccessTypeEverybody,
},
},
CodeBytes: wasmIdent,
},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{"1", `{}`})
flagSet := cmd.Flags()
flagSet.Set("label", "testing")
flagSet.Set("run-as", keeper.RandomBech32AccountAddress(t))
flagSet.Set("amount", "10stake")
flagSet.Set("no-admin", "true")
},
expError: true,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
homeDir := setupGenesis(t, spec.srcGenesis)
// when
cmd := GenesisInstantiateContractCmd(homeDir, NewDefaultGenesisIO())
spec.mutator(cmd)
err := executeCmdWithContext(t, homeDir, cmd)
if spec.expError {
require.Error(t, err)
return
}
require.NoError(t, err)
// then
moduleState := loadModuleState(t, homeDir)
assert.Len(t, moduleState.GenMsgs, spec.expMsgCount)
})
}
}
func TestExecuteContractCmd(t *testing.T) {
const firstContractAddress = "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"
minimalWasmGenesis := types.GenesisState{
Params: types.DefaultParams(),
}
anyValidWasmFile, err := ioutil.TempFile(t.TempDir(), "wasm")
require.NoError(t, err)
anyValidWasmFile.Write(wasmIdent)
require.NoError(t, anyValidWasmFile.Close())
specs := map[string]struct {
srcGenesis types.GenesisState
mutator func(cmd *cobra.Command)
expMsgCount int
expError bool
}{
"all good with contract in genesis contracts": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfoFixture(),
CodeBytes: wasmIdent,
},
},
Contracts: []types.Contract{
{
ContractAddress: firstContractAddress,
ContractInfo: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Created = nil
}),
ContractState: []types.Model{},
},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{firstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
},
expMsgCount: 1,
},
"all good with contract from genesis store messages without initial sequence": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfoFixture(),
CodeBytes: wasmIdent,
},
},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: types.MsgInstantiateContractFixture()}},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{firstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
},
expMsgCount: 2,
},
"all good with contract from genesis store messages and contract sequence set": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfoFixture(),
CodeBytes: wasmIdent,
},
},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: types.MsgInstantiateContractFixture()}},
},
Sequences: []types.Sequence{
{IDKey: types.KeyLastInstanceID, Value: 100},
},
},
mutator: func(cmd *cobra.Command) {
// See TestBuildContractAddress in keeper_test.go
cmd.SetArgs([]string{"cosmos1mujpjkwhut9yjw4xueyugc02evfv46y0dtmnz4lh8xxkkdapym9stu5qm8", `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
},
expMsgCount: 2,
},
"fails with unknown contract address": {
srcGenesis: minimalWasmGenesis,
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{keeper.RandomBech32AccountAddress(t), `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
},
expError: true,
},
"succeeds with unknown account when no funds": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfoFixture(),
CodeBytes: wasmIdent,
},
},
Contracts: []types.Contract{
{
ContractAddress: firstContractAddress,
ContractInfo: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Created = nil
}),
ContractState: []types.Model{},
},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{firstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", keeper.RandomBech32AccountAddress(t))
},
expMsgCount: 1,
},
"succeeds with funds from well funded account": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfoFixture(),
CodeBytes: wasmIdent,
},
},
Contracts: []types.Contract{
{
ContractAddress: firstContractAddress,
ContractInfo: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Created = nil
}),
ContractState: []types.Model{},
},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{firstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", myWellFundedAccount)
flagSet.Set("amount", "100stake")
},
expMsgCount: 1,
},
"fails without enough sender balance": {
srcGenesis: types.GenesisState{
Params: types.DefaultParams(),
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: types.CodeInfoFixture(),
CodeBytes: wasmIdent,
},
},
Contracts: []types.Contract{
{
ContractAddress: firstContractAddress,
ContractInfo: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Created = nil
}),
ContractState: []types.Model{},
},
},
},
mutator: func(cmd *cobra.Command) {
cmd.SetArgs([]string{firstContractAddress, `{}`})
flagSet := cmd.Flags()
flagSet.Set("run-as", keeper.RandomBech32AccountAddress(t))
flagSet.Set("amount", "10stake")
},
expError: true,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
homeDir := setupGenesis(t, spec.srcGenesis)
cmd := GenesisExecuteContractCmd(homeDir, NewDefaultGenesisIO())
spec.mutator(cmd)
// when
err := executeCmdWithContext(t, homeDir, cmd)
if spec.expError {
require.Error(t, err)
return
}
require.NoError(t, err)
// then
moduleState := loadModuleState(t, homeDir)
assert.Len(t, moduleState.GenMsgs, spec.expMsgCount)
})
}
}
func TestGetAllContracts(t *testing.T) {
specs := map[string]struct {
src types.GenesisState
exp []contractMeta
}{
"read from contracts state": {
src: types.GenesisState{
Contracts: []types.Contract{
{
ContractAddress: "first-contract",
ContractInfo: types.ContractInfo{Label: "first"},
},
{
ContractAddress: "second-contract",
ContractInfo: types.ContractInfo{Label: "second"},
},
},
},
exp: []contractMeta{
{
ContractAddress: "first-contract",
Info: types.ContractInfo{Label: "first"},
},
{
ContractAddress: "second-contract",
Info: types.ContractInfo{Label: "second"},
},
},
},
"read from message state": {
src: types.GenesisState{
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Label: "first"}}},
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Label: "second"}}},
},
},
exp: []contractMeta{
{
ContractAddress: keeper.BuildContractAddress(0, 1).String(),
Info: types.ContractInfo{Label: "first"},
},
{
ContractAddress: keeper.BuildContractAddress(0, 2).String(),
Info: types.ContractInfo{Label: "second"},
},
},
},
"read from message state with contract sequence": {
src: types.GenesisState{
Sequences: []types.Sequence{
{IDKey: types.KeyLastInstanceID, Value: 100},
},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Label: "hundred"}}},
},
},
exp: []contractMeta{
{
ContractAddress: keeper.BuildContractAddress(0, 100).String(),
Info: types.ContractInfo{Label: "hundred"},
},
},
},
"read from contract and message state with contract sequence": {
src: types.GenesisState{
Contracts: []types.Contract{
{
ContractAddress: "first-contract",
ContractInfo: types.ContractInfo{Label: "first"},
},
},
Sequences: []types.Sequence{
{IDKey: types.KeyLastInstanceID, Value: 100},
},
GenMsgs: []types.GenesisState_GenMsgs{
{Sum: &types.GenesisState_GenMsgs_InstantiateContract{InstantiateContract: &types.MsgInstantiateContract{Label: "hundred"}}},
},
},
exp: []contractMeta{
{
ContractAddress: "first-contract",
Info: types.ContractInfo{Label: "first"},
},
{
ContractAddress: keeper.BuildContractAddress(0, 100).String(),
Info: types.ContractInfo{Label: "hundred"},
},
},
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
got := getAllContracts(&spec.src)
assert.Equal(t, spec.exp, got)
})
}
}
func setupGenesis(t *testing.T, wasmGenesis types.GenesisState) string {
appCodec := keeper.MakeEncodingConfig(t).Marshaler
homeDir := t.TempDir()
require.NoError(t, os.Mkdir(path.Join(homeDir, "config"), 0700))
genFilename := path.Join(homeDir, "config", "genesis.json")
appState := make(map[string]json.RawMessage)
appState[types.ModuleName] = appCodec.MustMarshalJSON(&wasmGenesis)
bankGenesis := banktypes.DefaultGenesisState()
bankGenesis.Balances = append(bankGenesis.Balances, banktypes.Balance{
// add a balance for the default sender account
Address: myWellFundedAccount,
Coins: sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(10000000000))),
})
appState[banktypes.ModuleName] = appCodec.MustMarshalJSON(bankGenesis)
appState[stakingtypes.ModuleName] = appCodec.MustMarshalJSON(stakingtypes.DefaultGenesisState())
appStateBz, err := json.Marshal(appState)
require.NoError(t, err)
genDoc := tmtypes.GenesisDoc{
ChainID: "testing",
AppState: appStateBz,
}
err = genutil.ExportGenesisFile(&genDoc, genFilename)
require.NoError(t, err)
return homeDir
}
func executeCmdWithContext(t *testing.T, homeDir string, cmd *cobra.Command) error {
logger := log.NewNopLogger()
cfg, err := genutiltest.CreateDefaultTendermintConfig(homeDir)
require.NoError(t, err)
appCodec := keeper.MakeEncodingConfig(t).Marshaler
serverCtx := server.NewContext(viper.New(), cfg, logger)
clientCtx := client.Context{}.WithCodec(appCodec).WithHomeDir(homeDir)
ctx := context.Background()
ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx)
ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx)
flagSet := cmd.Flags()
flagSet.Set("home", homeDir)
flagSet.Set(flags.FlagKeyringBackend, keyring.BackendTest)
mockIn := testutil.ApplyMockIODiscardOutErr(cmd)
kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, homeDir, mockIn)
require.NoError(t, err)
_, err = kb.NewAccount(defaultTestKeyName, testdata.TestMnemonic, "", sdk.FullFundraiserPath, hd.Secp256k1)
require.NoError(t, err)
return cmd.ExecuteContext(ctx)
}
func loadModuleState(t *testing.T, homeDir string) types.GenesisState {
genFilename := path.Join(homeDir, "config", "genesis.json")
appState, _, err := genutiltypes.GenesisStateFromGenFile(genFilename)
require.NoError(t, err)
require.Contains(t, appState, types.ModuleName)
appCodec := keeper.MakeEncodingConfig(t).Marshaler
var moduleState types.GenesisState
require.NoError(t, appCodec.UnmarshalJSON(appState[types.ModuleName], &moduleState))
return moduleState
}