Rework query result type; tests

This commit is contained in:
Alex Peters 2020-01-14 17:04:45 +01:00
parent 9156f29a72
commit f051d24fe8
No known key found for this signature in database
GPG Key ID: BD28388D49EE708D
7 changed files with 231 additions and 34 deletions

1
go.mod
View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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())
}

View File

@ -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)
}
})
}
}

View File

@ -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

View File

@ -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) {