From c6aac95b693bf8e5c12abfd26b8363ef0d0b3968 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Mon, 13 Jan 2020 17:46:12 +0100 Subject: [PATCH 1/7] Add 'raw, all' queries --- x/wasm/client/cli/query.go | 50 +++++++++++++++++++++++++++++-- x/wasm/internal/keeper/querier.go | 20 +++++++++++-- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/x/wasm/client/cli/query.go b/x/wasm/client/cli/query.go index cf6cf7332f..840ff985d0 100644 --- a/x/wasm/client/cli/query.go +++ b/x/wasm/client/cli/query.go @@ -2,6 +2,7 @@ package cli import ( "encoding/json" + "errors" "fmt" "io/ioutil" "strconv" @@ -145,10 +146,26 @@ func GetCmdGetContractInfo(cdc *codec.Codec) *cobra.Command { // GetCmdGetContractState dumps full internal state of a given contract func GetCmdGetContractState(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "contract-state", + Short: "Querying commands for the wasm module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + cmd.AddCommand(client.GetCommands( + GetCmdGetContractStateAll(cdc), + GetCmdGetContractStateRaw(cdc), + )...) + return cmd + +} + +func GetCmdGetContractStateAll(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ - Use: "contract-state [bech32_address]", - Short: "Prints out internal state of a contract given its address", - Long: "Prints out internal state of a contract given its address", + Use: "all [bech32_address]", + Short: "Prints out all internal state of a contract given its address", + Long: "Prints out all internal state of a contract given its address", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) @@ -168,3 +185,30 @@ func GetCmdGetContractState(cdc *codec.Codec) *cobra.Command { }, } } +func GetCmdGetContractStateRaw(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "raw [bech32_address] [key]", + Short: "Prints out internal state for key of a contract given its address", + Long: "Prints out internal state for of a contract given its address", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + addr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + key := args[1] + if key == "" { + return errors.New("key must not be empty") + } + route := fmt.Sprintf("custom/%s/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String(), key) + res, _, err := cliCtx.Query(route) + if err != nil { + return err + } + fmt.Println(string(res)) + return nil + }, + } +} diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index 8ec4b89515..8a592d6e31 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -1,6 +1,7 @@ package keeper import ( + "bytes" "encoding/json" "strconv" @@ -29,7 +30,18 @@ func NewQuerier(keeper Keeper) sdk.Querier { case QueryListContracts: return queryContractList(ctx, req, keeper) case QueryGetContractState: - return queryContractState(ctx, path[1], req, keeper) + var accept func([]byte) bool + if len(path) > 2 { // passed with key + binKey := []byte(path[2]) + accept = func(b []byte) bool { + return bytes.Equal(b, binKey) + } + } else { + accept = func(b []byte) bool { + return true + } + } + return selectContractState(ctx, path[1], keeper, accept) case QueryGetCode: return queryCode(ctx, path[1], req, keeper) case QueryListCode: @@ -67,7 +79,7 @@ func queryContractList(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([ return bz, nil } -func queryContractState(ctx sdk.Context, bech string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func selectContractState(ctx sdk.Context, bech string, keeper Keeper, accept func([]byte) bool) ([]byte, sdk.Error) { addr, err := sdk.AccAddressFromBech32(bech) if err != nil { return nil, sdk.ErrUnknownRequest(err.Error()) @@ -76,13 +88,15 @@ func queryContractState(ctx sdk.Context, bech string, req abci.RequestQuery, kee var state []types.Model for ; iter.Valid(); iter.Next() { + if !accept(iter.Key()) { + continue + } m := types.Model{ Key: string(iter.Key()), Value: string(iter.Value()), } state = append(state, m) } - bz, err := json.MarshalIndent(state, "", " ") if err != nil { return nil, sdk.ErrUnknownRequest(err.Error()) From 9156f29a7231fe728612dcf64df8cfe589d1e539 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 14 Jan 2020 13:47:42 +0100 Subject: [PATCH 2/7] Query contract state variations --- x/wasm/client/cli/query.go | 37 +++++++++++++++- x/wasm/internal/keeper/keeper.go | 74 ++++++++++++++++++++++--------- x/wasm/internal/keeper/querier.go | 59 ++++++++++++++---------- x/wasm/module_test.go | 3 +- 4 files changed, 125 insertions(+), 48 deletions(-) diff --git a/x/wasm/client/cli/query.go b/x/wasm/client/cli/query.go index 840ff985d0..a5985cf091 100644 --- a/x/wasm/client/cli/query.go +++ b/x/wasm/client/cli/query.go @@ -156,6 +156,7 @@ func GetCmdGetContractState(cdc *codec.Codec) *cobra.Command { cmd.AddCommand(client.GetCommands( GetCmdGetContractStateAll(cdc), GetCmdGetContractStateRaw(cdc), + GetCmdGetContractStateSmart(cdc), )...) return cmd @@ -185,6 +186,7 @@ func GetCmdGetContractStateAll(cdc *codec.Codec) *cobra.Command { }, } } + func GetCmdGetContractStateRaw(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "raw [bech32_address] [key]", @@ -202,11 +204,42 @@ func GetCmdGetContractStateRaw(cdc *codec.Codec) *cobra.Command { if key == "" { return errors.New("key must not be empty") } - route := fmt.Sprintf("custom/%s/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String(), key) - res, _, err := cliCtx.Query(route) + route := fmt.Sprintf("custom/%s/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String(), keeper.QueryMethodContractStateRaw) + queryData := []byte(key) // todo: open question: encode into json??? + res, _, err := cliCtx.QueryWithData(route, queryData) + if err != nil { + return err + } + fmt.Println(string(res)) + return nil + }, + } +} + +func GetCmdGetContractStateSmart(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "smart [bech32_address] [query]", + Short: "Calls contract with given address with query data and prints the returned result", + Long: "Calls contract with given address with query data and prints the returned result", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + addr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + key := args[1] + if key == "" { + return errors.New("key must not be empty") + } + route := fmt.Sprintf("custom/%s/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String(), keeper.QueryMethodContractStateSmart) + var queryData []byte + res, _, err := cliCtx.QueryWithData(route, queryData) if err != nil { return err } + // todo: decode response fmt.Println(string(res)) return nil }, diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 1996e908aa..440c1b70f8 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -27,6 +27,8 @@ const GasMultiplier = 100 // MaxGas for a contract is 900 million (enforced in rust) const MaxGas = 900_000_000 +const smartQueryGasLimit = 3000000 // Todo: should be set by app.toml + // Keeper will have a reference to Wasmer with it's own data directory. type Keeper struct { storeKey sdk.StoreKey @@ -37,6 +39,8 @@ type Keeper struct { router sdk.Router wasmer wasm.Wasmer + // queryGasLimit is the max wasm gas that can be spent on executing a query with a contract + queryGasLimit uint64 } // NewKeeper creates a new contract Keeper instance @@ -53,6 +57,7 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, accountKeeper auth.Accou accountKeeper: accountKeeper, bankKeeper: bankKeeper, router: router, + queryGasLimit: smartQueryGasLimit, } } @@ -128,22 +133,10 @@ func (k Keeper) Instantiate(ctx sdk.Context, creator sdk.AccAddress, codeID uint // Execute executes the contract instance func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, coins sdk.Coins, msgs []byte) (sdk.Result, sdk.Error) { - store := ctx.KVStore(k.storeKey) - - contractBz := store.Get(types.GetContractAddressKey(contractAddress)) - if contractBz == nil { - return sdk.Result{}, types.ErrNotFound("contract") - } - var contract types.ContractInfo - k.cdc.MustUnmarshalBinaryBare(contractBz, &contract) - - contractInfoBz := store.Get(types.GetCodeKey(contract.CodeID)) - if contractInfoBz == nil { - return sdk.Result{}, types.ErrNotFound("contract info") + codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress) + if err != nil { + return sdk.Result{}, err } - var codeInfo types.CodeInfo - k.cdc.MustUnmarshalBinaryBare(contractInfoBz, &codeInfo) - // add more funds sdkerr := k.bankKeeper.SendCoins(ctx, caller, contractAddress, coins) if sdkerr != nil { @@ -152,13 +145,10 @@ func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller contractAccount := k.accountKeeper.GetAccount(ctx, contractAddress) params := types.NewParams(ctx, caller, coins, contractAccount) - prefixStoreKey := types.GetContractStorePrefixKey(contractAddress) - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) - gas := gasForContract(ctx) - res, err := k.wasmer.Execute(codeInfo.CodeHash, params, msgs, prefixStore, gas) - if err != nil { - return sdk.Result{}, types.ErrExecuteFailed(err) + res, execErr := k.wasmer.Execute(codeInfo.CodeHash, params, msgs, prefixStore, gas) + if execErr != nil { + return sdk.Result{}, types.ErrExecuteFailed(execErr) } consumeGas(ctx, res.GasUsed) @@ -170,6 +160,42 @@ func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller return types.CosmosResult(*res), nil } +func (k Keeper) Query(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) (*wasmTypes.QueryResult, sdk.Error) { + ctx = ctx.WithGasMeter(sdk.NewGasMeter(k.queryGasLimit)) + + codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) + if err != nil { + return nil, err + } + res, gasUsed, qErr := k.wasmer.Query(codeInfo.CodeHash, req, prefixStore, gasForContract(ctx)) + if qErr != nil { + return nil, types.ErrExecuteFailed(qErr) + } + consumeGas(ctx, gasUsed) + return res, nil +} + +func (k Keeper) contractInstance(ctx sdk.Context, contractAddress sdk.AccAddress) (types.CodeInfo, prefix.Store, sdk.Error) { + store := ctx.KVStore(k.storeKey) + + contractBz := store.Get(types.GetContractAddressKey(contractAddress)) + if contractBz == nil { + return types.CodeInfo{}, prefix.Store{}, types.ErrNotFound("contract") + } + var contract types.ContractInfo + k.cdc.MustUnmarshalBinaryBare(contractBz, &contract) + + contractInfoBz := store.Get(types.GetCodeKey(contract.CodeID)) + if contractInfoBz == nil { + return types.CodeInfo{}, prefix.Store{}, types.ErrNotFound("contract info") + } + var codeInfo types.CodeInfo + k.cdc.MustUnmarshalBinaryBare(contractInfoBz, &codeInfo) + prefixStoreKey := types.GetContractStorePrefixKey(contractAddress) + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) + return codeInfo, prefixStore, nil +} + func (k Keeper) GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { store := ctx.KVStore(k.storeKey) var contract types.ContractInfo @@ -205,6 +231,12 @@ func (k Keeper) GetContractState(ctx sdk.Context, contractAddress sdk.AccAddress return prefixStore.Iterator(nil, nil) } +func (k Keeper) getContractStateForKey(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []byte { + prefixStoreKey := types.GetContractStorePrefixKey(contractAddress) + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) + return prefixStore.Get(key) +} + func (k Keeper) setContractState(ctx sdk.Context, contractAddress sdk.AccAddress, models []types.Model) { prefixStoreKey := types.GetContractStorePrefixKey(contractAddress) prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index 8a592d6e31..696ca23665 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -1,7 +1,6 @@ package keeper import ( - "bytes" "encoding/json" "strconv" @@ -21,6 +20,12 @@ const ( QueryListCode = "list-code" ) +const ( + QueryMethodContractStateSmart = "smart" + QueryMethodContractStateAll = "all" + QueryMethodContractStateRaw = "raw" +) + // NewQuerier creates a new querier func NewQuerier(keeper Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { @@ -30,18 +35,10 @@ func NewQuerier(keeper Keeper) sdk.Querier { case QueryListContracts: return queryContractList(ctx, req, keeper) case QueryGetContractState: - var accept func([]byte) bool - if len(path) > 2 { // passed with key - binKey := []byte(path[2]) - accept = func(b []byte) bool { - return bytes.Equal(b, binKey) - } - } else { - accept = func(b []byte) bool { - return true - } + if len(path) < 3 { + return nil, sdk.ErrUnknownRequest("unknown data query endpoint") } - return selectContractState(ctx, path[1], keeper, accept) + return queryContractState(ctx, path[1], path[2], req, keeper) case QueryGetCode: return queryCode(ctx, path[1], req, keeper) case QueryListCode: @@ -79,25 +76,39 @@ func queryContractList(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([ return bz, nil } -func selectContractState(ctx sdk.Context, bech string, keeper Keeper, accept func([]byte) bool) ([]byte, sdk.Error) { - addr, err := sdk.AccAddressFromBech32(bech) +func queryContractState(ctx sdk.Context, bech, queryMethod string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { + contractAddr, err := sdk.AccAddressFromBech32(bech) if err != nil { return nil, sdk.ErrUnknownRequest(err.Error()) } - iter := keeper.GetContractState(ctx, addr) - var state []types.Model - for ; iter.Valid(); iter.Next() { - if !accept(iter.Key()) { - continue + var result interface{} + switch queryMethod { + case QueryMethodContractStateAll: + var state []types.Model + for iter := keeper.GetContractState(ctx, contractAddr); iter.Valid(); iter.Next() { + state = append(state, types.Model{ + Key: string(iter.Key()), + Value: string(iter.Value()), + }) } - m := types.Model{ - Key: string(iter.Key()), - Value: string(iter.Value()), + result = state + case QueryMethodContractStateRaw: + value := keeper.getContractStateForKey(ctx, contractAddr, req.Data) + result = []types.Model{{ + Key: string(req.Data), + Value: string(value), + }} + case QueryMethodContractStateSmart: + res, err := keeper.Query(ctx, contractAddr, req.Data) + if err != nil { + return nil, err } - state = append(state, m) + result = res.Results + default: + return nil, sdk.ErrUnknownRequest("unsupported data query method for contract-state") } - bz, err := json.MarshalIndent(state, "", " ") + bz, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, sdk.ErrUnknownRequest(err.Error()) } diff --git a/x/wasm/module_test.go b/x/wasm/module_test.go index eb9d83a6c8..a2e6c22c04 100644 --- a/x/wasm/module_test.go +++ b/x/wasm/module_test.go @@ -7,6 +7,7 @@ import ( "os" "testing" + "github.com/cosmwasm/wasmd/x/wasm/internal/keeper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -397,7 +398,7 @@ type model struct { } func assertContractState(t *testing.T, q sdk.Querier, ctx sdk.Context, addr sdk.AccAddress, expected state) { - path := []string{QueryGetContractState, addr.String()} + path := []string{QueryGetContractState, addr.String(), keeper.QueryMethodContractStateAll} bz, sdkerr := q(ctx, path, abci.RequestQuery{}) require.NoError(t, sdkerr) From f051d24fe8f98775db2e727044fabd678ab3b640 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 14 Jan 2020 17:04:45 +0100 Subject: [PATCH 3/7] Rework query result type; tests --- go.mod | 1 + x/wasm/client/cli/query.go | 78 ++++++++++++--- x/wasm/internal/keeper/keeper.go | 33 +++++-- x/wasm/internal/keeper/querier.go | 18 ++-- x/wasm/internal/keeper/querier_test.go | 131 +++++++++++++++++++++++++ x/wasm/internal/types/types.go | 2 +- x/wasm/module_test.go | 2 +- 7 files changed, 231 insertions(+), 34 deletions(-) create mode 100644 x/wasm/internal/keeper/querier_test.go diff --git a/go.mod b/go.mod index b5c0f1b080..fd2ce8696c 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa github.com/spf13/afero v1.2.2 // indirect github.com/spf13/cobra v0.0.5 + github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.5.0 github.com/stretchr/testify v1.4.0 github.com/tendermint/go-amino v0.15.1 diff --git a/x/wasm/client/cli/query.go b/x/wasm/client/cli/query.go index a5985cf091..6ba0f80b17 100644 --- a/x/wasm/client/cli/query.go +++ b/x/wasm/client/cli/query.go @@ -1,12 +1,16 @@ package cli import ( + "encoding/base64" + "encoding/hex" "encoding/json" "errors" "fmt" "io/ioutil" "strconv" + flag "github.com/spf13/pflag" + "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" @@ -176,7 +180,7 @@ func GetCmdGetContractStateAll(cdc *codec.Codec) *cobra.Command { return err } - route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String()) + route := fmt.Sprintf("custom/%s/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String(), keeper.QueryMethodContractStateAll) res, _, err := cliCtx.Query(route) if err != nil { return err @@ -188,24 +192,24 @@ func GetCmdGetContractStateAll(cdc *codec.Codec) *cobra.Command { } func GetCmdGetContractStateRaw(cdc *codec.Codec) *cobra.Command { - return &cobra.Command{ + decoder := newArgDecoder(hex.DecodeString) + cmd := &cobra.Command{ Use: "raw [bech32_address] [key]", Short: "Prints out internal state for key of a contract given its address", Long: "Prints out internal state for of a contract given its address", Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) addr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } - key := args[1] - if key == "" { - return errors.New("key must not be empty") + queryData, err := decoder.DecodeString(args[1]) + if err != nil { + return err } route := fmt.Sprintf("custom/%s/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String(), keeper.QueryMethodContractStateRaw) - queryData := []byte(key) // todo: open question: encode into json??? res, _, err := cliCtx.QueryWithData(route, queryData) if err != nil { return err @@ -214,15 +218,19 @@ func GetCmdGetContractStateRaw(cdc *codec.Codec) *cobra.Command { return nil }, } + decoder.RegisterFlags(cmd.PersistentFlags(), "key argument") + return cmd } func GetCmdGetContractStateSmart(cdc *codec.Codec) *cobra.Command { - return &cobra.Command{ + decoder := newArgDecoder(asciiDecodeString) + + cmd := &cobra.Command{ Use: "smart [bech32_address] [query]", Short: "Calls contract with given address with query data and prints the returned result", Long: "Calls contract with given address with query data and prints the returned result", Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) addr, err := sdk.AccAddressFromBech32(args[0]) @@ -234,14 +242,62 @@ func GetCmdGetContractStateSmart(cdc *codec.Codec) *cobra.Command { return errors.New("key must not be empty") } route := fmt.Sprintf("custom/%s/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String(), keeper.QueryMethodContractStateSmart) - var queryData []byte + + queryData, err := decoder.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("decode query: %s", err) + } res, _, err := cliCtx.QueryWithData(route, queryData) if err != nil { return err } - // todo: decode response fmt.Println(string(res)) return nil }, } + decoder.RegisterFlags(cmd.PersistentFlags(), "query argument") + return cmd +} + +type argumentDecoder struct { + // dec is the default decoder + dec func(string) ([]byte, error) + asciiF, hexF, b64F bool +} + +func newArgDecoder(def func(string) ([]byte, error)) *argumentDecoder { + return &argumentDecoder{dec: def} +} + +func (a *argumentDecoder) RegisterFlags(f *flag.FlagSet, argName string) { + f.BoolVar(&a.asciiF, "ascii", false, "ascii encoded "+argName) + f.BoolVar(&a.hexF, "hex", false, "hex encoded "+argName) + f.BoolVar(&a.b64F, "b64", false, "base64 encoded "+argName) +} + +func (a *argumentDecoder) DecodeString(s string) ([]byte, error) { + found := -1 + for i, v := range []*bool{&a.asciiF, &a.hexF, &a.b64F} { + if !*v { + continue + } + if found != -1 { + return nil, errors.New("multiple decoding flags used") + } + found = i + } + switch found { + case 0: + return asciiDecodeString(s) + case 1: + return hex.DecodeString(s) + case 2: + return base64.StdEncoding.DecodeString(s) + default: + return a.dec(s) + } +} + +func asciiDecodeString(s string) ([]byte, error) { + return []byte(s), nil } diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 440c1b70f8..cd66c93f66 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -160,19 +160,40 @@ func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller return types.CosmosResult(*res), nil } -func (k Keeper) Query(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) (*wasmTypes.QueryResult, sdk.Error) { +// QuerySmart queries the smart contract itself. +func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]types.Model, sdk.Error) { ctx = ctx.WithGasMeter(sdk.NewGasMeter(k.queryGasLimit)) codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) if err != nil { return nil, err } - res, gasUsed, qErr := k.wasmer.Query(codeInfo.CodeHash, req, prefixStore, gasForContract(ctx)) + queryResult, gasUsed, qErr := k.wasmer.Query(codeInfo.CodeHash, req, prefixStore, gasForContract(ctx)) if qErr != nil { return nil, types.ErrExecuteFailed(qErr) } consumeGas(ctx, gasUsed) - return res, nil + models := make([]types.Model, len(queryResult.Results)) + for i := range queryResult.Results { + models[i] = types.Model{ + Key: queryResult.Results[i].Key, + Value: string(queryResult.Results[i].Value), + } + } + return models, nil +} + +// QueryRaw returns the contract's state for give key. For a `nil` key a `nil` result is returned. +func (k Keeper) QueryRaw(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []types.Model { + if key == nil { + return nil + } + prefixStoreKey := types.GetContractStorePrefixKey(contractAddress) + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) + return []types.Model{{ + Key: string(key), + Value: string(prefixStore.Get(key)), + }} } func (k Keeper) contractInstance(ctx sdk.Context, contractAddress sdk.AccAddress) (types.CodeInfo, prefix.Store, sdk.Error) { @@ -231,12 +252,6 @@ func (k Keeper) GetContractState(ctx sdk.Context, contractAddress sdk.AccAddress return prefixStore.Iterator(nil, nil) } -func (k Keeper) getContractStateForKey(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []byte { - prefixStoreKey := types.GetContractStorePrefixKey(contractAddress) - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) - return prefixStore.Get(key) -} - func (k Keeper) setContractState(ctx sdk.Context, contractAddress sdk.AccAddress, models []types.Model) { prefixStoreKey := types.GetContractStorePrefixKey(contractAddress) prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index 696ca23665..d9355fe79b 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -82,33 +82,27 @@ func queryContractState(ctx sdk.Context, bech, queryMethod string, req abci.Requ return nil, sdk.ErrUnknownRequest(err.Error()) } - var result interface{} + var resultData []types.Model switch queryMethod { case QueryMethodContractStateAll: - var state []types.Model for iter := keeper.GetContractState(ctx, contractAddr); iter.Valid(); iter.Next() { - state = append(state, types.Model{ + resultData = append(resultData, types.Model{ Key: string(iter.Key()), Value: string(iter.Value()), }) } - result = state case QueryMethodContractStateRaw: - value := keeper.getContractStateForKey(ctx, contractAddr, req.Data) - result = []types.Model{{ - Key: string(req.Data), - Value: string(value), - }} + resultData = keeper.QueryRaw(ctx, contractAddr, req.Data) case QueryMethodContractStateSmart: - res, err := keeper.Query(ctx, contractAddr, req.Data) + res, err := keeper.QuerySmart(ctx, contractAddr, req.Data) if err != nil { return nil, err } - result = res.Results + resultData = res default: return nil, sdk.ErrUnknownRequest("unsupported data query method for contract-state") } - bz, err := json.MarshalIndent(result, "", " ") + bz, err := json.MarshalIndent(resultData, "", " ") if err != nil { return nil, sdk.ErrUnknownRequest(err.Error()) } diff --git a/x/wasm/internal/keeper/querier_test.go b/x/wasm/internal/keeper/querier_test.go new file mode 100644 index 0000000000..c8e07664a5 --- /dev/null +++ b/x/wasm/internal/keeper/querier_test.go @@ -0,0 +1,131 @@ +package keeper + +import ( + "encoding/json" + "io/ioutil" + "os" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmwasm/wasmd/x/wasm/internal/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +func TestQueryContractState(t *testing.T) { + type model struct { + Key string `json:"key"` + Value string `json:"val"` + } + + tempDir, err := ioutil.TempDir("", "wasm") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir) + + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) + creator := createFakeFundedAccount(ctx, accKeeper, deposit.Add(deposit)) + anyAddr := createFakeFundedAccount(ctx, accKeeper, topUp) + + wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm") + require.NoError(t, err) + + contractID, err := keeper.Create(ctx, creator, wasmCode) + require.NoError(t, err) + + _, _, bob := keyPubAddr() + initMsg := InitMsg{ + Verifier: anyAddr.String(), + Beneficiary: bob.String(), + } + initMsgBz, err := json.Marshal(initMsg) + require.NoError(t, err) + + addr, err := keeper.Instantiate(ctx, creator, contractID, initMsgBz, deposit) + require.NoError(t, err) + require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String()) + + contractModel := []types.Model{ + {Key: "foo", Value: "bar"}, + {Key: string([]byte{0x0, 0x1}), Value: string([]byte{0x2, 0x3})}, + } + keeper.setContractState(ctx, addr, contractModel) + + q := NewQuerier(keeper) + specs := map[string]struct { + srcPath []string + srcReq abci.RequestQuery + expModelLen int + expModelContains []model + expErr sdk.Error + }{ + "query all": { + srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateAll}, + expModelLen: 3, + expModelContains: []model{ + {Key: "foo", Value: "bar"}, + {Key: string([]byte{0x0, 0x1}), Value: string([]byte{0x2, 0x3})}, + }, + }, + "query raw key": { + srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateRaw}, + srcReq: abci.RequestQuery{Data: []byte("foo")}, + expModelLen: 1, + expModelContains: []model{{Key: "foo", Value: "bar"}}, + }, + "query raw binary key": { + srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateRaw}, + srcReq: abci.RequestQuery{Data: []byte{0x0, 0x1}}, + expModelLen: 1, + expModelContains: []model{{Key: string([]byte{0x0, 0x1}), Value: string([]byte{0x2, 0x3})}}, + }, + "query smart": { + srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateSmart}, + srcReq: abci.RequestQuery{Data: []byte(`{"raw":{"key":"config"}}`)}, + expModelLen: 1, + //expModelContains: []model{}, // stopping here as contract internals are not stable + }, + "query unknown raw key": { + srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateRaw}, + srcReq: abci.RequestQuery{Data: []byte("unknown")}, + expModelLen: 1, + expModelContains: []model{{Key: "unknown", Value: ""}}, + }, + "query empty raw key": { + srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateRaw}, + expModelLen: 0, + }, + "query raw with unknown address": { + srcPath: []string{QueryGetContractState, anyAddr.String(), QueryMethodContractStateRaw}, + expModelLen: 0, + }, + "query all with unknown address": { + srcPath: []string{QueryGetContractState, anyAddr.String(), QueryMethodContractStateAll}, + expModelLen: 0, + }, + "query smart with unknown address": { + srcPath: []string{QueryGetContractState, anyAddr.String(), QueryMethodContractStateSmart}, + expModelLen: 0, + expErr: types.ErrNotFound("contract"), + }, + } + + for msg, spec := range specs { + t.Run(msg, func(t *testing.T) { + binResult, err := q(ctx, spec.srcPath, spec.srcReq) + require.Equal(t, spec.expErr, err) + // then + var r []model + if spec.expErr == nil { + require.NoError(t, json.Unmarshal(binResult, &r)) + } + require.Len(t, r, spec.expModelLen) + // and in result set + for _, v := range spec.expModelContains { + assert.Contains(t, r, v) + } + }) + } +} diff --git a/x/wasm/internal/types/types.go b/x/wasm/internal/types/types.go index 852a9b4c81..7e5ffe73b4 100644 --- a/x/wasm/internal/types/types.go +++ b/x/wasm/internal/types/types.go @@ -9,7 +9,7 @@ import ( // Model is a struct that holds a KV pair type Model struct { Key string `json:"key"` - Value string `json:"value"` + Value string `json:"val"` } // CodeInfo is data for the uploaded contract WASM code diff --git a/x/wasm/module_test.go b/x/wasm/module_test.go index a2e6c22c04..21c68ffe44 100644 --- a/x/wasm/module_test.go +++ b/x/wasm/module_test.go @@ -394,7 +394,7 @@ func assertContractList(t *testing.T, q sdk.Querier, ctx sdk.Context, addrs []st type model struct { Key string `json:"key"` - Value string `json:"value"` + Value string `json:"val"` } func assertContractState(t *testing.T, q sdk.Querier, ctx sdk.Context, addr sdk.AccAddress, expected state) { From be63000e956367fcc221d6549fb56c0001615d6d Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 14 Jan 2020 17:16:26 +0100 Subject: [PATCH 4/7] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ece140c461..a2dd7cec2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] ### Features - +* (wasmd)[\#2](https://github.com/cosmwasm/wasmd/pull/22) Improve wasm contract queries (all, raw, smart) * (wasmd) [\#119](https://github.com/cosmwasm/wasmd/pull/119) Add support for the `--inter-block-cache` CLI flag and configuration. * (wasmcli) [\#132](https://github.com/cosmwasm/wasmd/pull/132) Add `tx decode` command to decode From 1874a6c5808ad46d7d54ec2e503d387ee05757a4 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 14 Jan 2020 17:19:56 +0100 Subject: [PATCH 5/7] Update testnet doc --- docs/deploy-testnet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploy-testnet.md b/docs/deploy-testnet.md index bee5e983ac..fda5642f9a 100644 --- a/docs/deploy-testnet.md +++ b/docs/deploy-testnet.md @@ -95,7 +95,7 @@ sleep 3 wasmcli query wasm list-contracts CONTRACT=cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5 wasmcli query wasm contract $CONTRACT -wasmcli query wasm contract-state $CONTRACT +wasmcli query wasm contract-state all $CONTRACT wasmcli query account $CONTRACT # execute fails if wrong person From 6a76047d57f6ba63f57256a8d36c3e670e5fb03f Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 14 Jan 2020 21:31:55 +0100 Subject: [PATCH 6/7] Return empty result for unknown raw key query --- x/wasm/internal/keeper/keeper.go | 11 +++++++---- x/wasm/internal/keeper/querier_test.go | 7 +++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index cd66c93f66..3d1c98a94d 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -190,10 +190,13 @@ func (k Keeper) QueryRaw(ctx sdk.Context, contractAddress sdk.AccAddress, key [] } prefixStoreKey := types.GetContractStorePrefixKey(contractAddress) prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) - return []types.Model{{ - Key: string(key), - Value: string(prefixStore.Get(key)), - }} + if val := prefixStore.Get(key); val != nil { + return []types.Model{{ + Key: string(key), + Value: string(val), + }} + } + return []types.Model{} } func (k Keeper) contractInstance(ctx sdk.Context, contractAddress sdk.AccAddress) (types.CodeInfo, prefix.Store, sdk.Error) { diff --git a/x/wasm/internal/keeper/querier_test.go b/x/wasm/internal/keeper/querier_test.go index c8e07664a5..7ab6f50b25 100644 --- a/x/wasm/internal/keeper/querier_test.go +++ b/x/wasm/internal/keeper/querier_test.go @@ -88,10 +88,9 @@ func TestQueryContractState(t *testing.T) { //expModelContains: []model{}, // stopping here as contract internals are not stable }, "query unknown raw key": { - srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateRaw}, - srcReq: abci.RequestQuery{Data: []byte("unknown")}, - expModelLen: 1, - expModelContains: []model{{Key: "unknown", Value: ""}}, + srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateRaw}, + srcReq: abci.RequestQuery{Data: []byte("unknown")}, + expModelLen: 0, }, "query empty raw key": { srcPath: []string{QueryGetContractState, addr.String(), QueryMethodContractStateRaw}, From a1e01e5df4d9e0c169bb59a39eacd769f22cf762 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 14 Jan 2020 21:45:43 +0100 Subject: [PATCH 7/7] ContrctQueries return non nil model slice --- x/wasm/internal/keeper/keeper.go | 12 +++++++----- x/wasm/internal/keeper/querier.go | 3 +++ x/wasm/internal/keeper/querier_test.go | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 3d1c98a94d..15a2f07abb 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -183,20 +183,22 @@ func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []b return models, nil } -// QueryRaw returns the contract's state for give key. For a `nil` key a `nil` result is returned. +// QueryRaw returns the contract's state for give key. For a `nil` key a empty slice` result is returned. func (k Keeper) QueryRaw(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []types.Model { + result := make([]types.Model, 0) if key == nil { - return nil + return result } prefixStoreKey := types.GetContractStorePrefixKey(contractAddress) prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) + if val := prefixStore.Get(key); val != nil { - return []types.Model{{ + return append(result, types.Model{ Key: string(key), Value: string(val), - }} + }) } - return []types.Model{} + return result } func (k Keeper) contractInstance(ctx sdk.Context, contractAddress sdk.AccAddress) (types.CodeInfo, prefix.Store, sdk.Error) { diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index d9355fe79b..76d06448ed 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -91,6 +91,9 @@ func queryContractState(ctx sdk.Context, bech, queryMethod string, req abci.Requ Value: string(iter.Value()), }) } + if resultData == nil { + resultData = make([]types.Model, 0) + } case QueryMethodContractStateRaw: resultData = keeper.QueryRaw(ctx, contractAddr, req.Data) case QueryMethodContractStateSmart: diff --git a/x/wasm/internal/keeper/querier_test.go b/x/wasm/internal/keeper/querier_test.go index 7ab6f50b25..eceb861e71 100644 --- a/x/wasm/internal/keeper/querier_test.go +++ b/x/wasm/internal/keeper/querier_test.go @@ -119,6 +119,7 @@ func TestQueryContractState(t *testing.T) { var r []model if spec.expErr == nil { require.NoError(t, json.Unmarshal(binResult, &r)) + require.NotNil(t, r) } require.Len(t, r, spec.expModelLen) // and in result set