Query contract state variations

This commit is contained in:
Alex Peters 2020-01-14 13:47:42 +01:00
parent c6aac95b69
commit 9156f29a72
No known key found for this signature in database
GPG Key ID: BD28388D49EE708D
4 changed files with 125 additions and 48 deletions

View File

@ -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,8 +204,9 @@ 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
}
@ -212,3 +215,33 @@ func GetCmdGetContractStateRaw(cdc *codec.Codec) *cobra.Command {
},
}
}
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
},
}
}

View File

@ -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")
codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress)
if err != nil {
return sdk.Result{}, err
}
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")
}
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)

View File

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

View File

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