Skip to content

Commit

Permalink
Merge pull request #22 from cosmwasm/improve_contract_queries_12
Browse files Browse the repository at this point in the history
Improve wasm contract queries
  • Loading branch information
ethanfrey authored Jan 15, 2020
2 parents eb78f8c + a1e01e5 commit 09016ea
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 42 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/deploy-testnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
141 changes: 137 additions & 4 deletions x/wasm/client/cli/query.go
Original file line number Diff line number Diff line change
@@ -1,11 +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"
Expand Down Expand Up @@ -145,10 +150,27 @@ 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),
GetCmdGetContractStateSmart(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)
Expand All @@ -158,7 +180,7 @@ func GetCmdGetContractState(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
Expand All @@ -168,3 +190,114 @@ func GetCmdGetContractState(cdc *codec.Codec) *cobra.Command {
},
}
}

func GetCmdGetContractStateRaw(cdc *codec.Codec) *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(_ *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)

addr, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}
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)
res, _, err := cliCtx.QueryWithData(route, queryData)
if err != nil {
return err
}
fmt.Println(string(res))
return nil
},
}
decoder.RegisterFlags(cmd.PersistentFlags(), "key argument")
return cmd
}

func GetCmdGetContractStateSmart(cdc *codec.Codec) *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(_ *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)

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
}
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
}
94 changes: 73 additions & 21 deletions x/wasm/internal/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -53,6 +57,7 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, accountKeeper auth.Accou
accountKeeper: accountKeeper,
bankKeeper: bankKeeper,
router: router,
queryGasLimit: smartQueryGasLimit,
}
}

Expand Down Expand Up @@ -132,22 +137,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 {
Expand All @@ -156,13 +149,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)

Expand All @@ -174,6 +164,68 @@ func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller
return types.CosmosResult(*res), nil
}

// 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
}
queryResult, gasUsed, qErr := k.wasmer.Query(codeInfo.CodeHash, req, prefixStore, gasForContract(ctx))
if qErr != nil {
return nil, types.ErrExecuteFailed(qErr)
}
consumeGas(ctx, gasUsed)
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 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 result
}
prefixStoreKey := types.GetContractStorePrefixKey(contractAddress)
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)

if val := prefixStore.Get(key); val != nil {
return append(result, types.Model{
Key: string(key),
Value: string(val),
})
}
return result
}

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
Expand Down
Loading

0 comments on commit 09016ea

Please sign in to comment.