mirror of https://github.com/certusone/wasmd.git
Rework query result type; tests
This commit is contained in:
parent
9156f29a72
commit
f051d24fe8
1
go.mod
1
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue