Skip to content

Commit 09016ea

Browse files
authored
Merge pull request #22 from cosmwasm/improve_contract_queries_12
Improve wasm contract queries
2 parents eb78f8c + a1e01e5 commit 09016ea

File tree

9 files changed

+382
-42
lines changed

9 files changed

+382
-42
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
3737
## [Unreleased]
3838

3939
### Features
40-
40+
* (wasmd)[\#2](https://github.com/cosmwasm/wasmd/pull/22) Improve wasm contract queries (all, raw, smart)
4141
* (wasmd) [\#119](https://github.com/cosmwasm/wasmd/pull/119) Add support for the `--inter-block-cache` CLI
4242
flag and configuration.
4343
* (wasmcli) [\#132](https://github.com/cosmwasm/wasmd/pull/132) Add `tx decode` command to decode

docs/deploy-testnet.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ sleep 3
9595
wasmcli query wasm list-contracts
9696
CONTRACT=cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5
9797
wasmcli query wasm contract $CONTRACT
98-
wasmcli query wasm contract-state $CONTRACT
98+
wasmcli query wasm contract-state all $CONTRACT
9999
wasmcli query account $CONTRACT
100100

101101
# execute fails if wrong person

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ require (
1919
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa
2020
github.com/spf13/afero v1.2.2 // indirect
2121
github.com/spf13/cobra v0.0.5
22+
github.com/spf13/pflag v1.0.5
2223
github.com/spf13/viper v1.5.0
2324
github.com/stretchr/testify v1.4.0
2425
github.com/tendermint/go-amino v0.15.1

x/wasm/client/cli/query.go

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package cli
22

33
import (
4+
"encoding/base64"
5+
"encoding/hex"
46
"encoding/json"
7+
"errors"
58
"fmt"
69
"io/ioutil"
710
"strconv"
811

12+
flag "github.com/spf13/pflag"
13+
914
"github.com/spf13/cobra"
1015

1116
"github.com/cosmos/cosmos-sdk/client"
@@ -145,10 +150,27 @@ func GetCmdGetContractInfo(cdc *codec.Codec) *cobra.Command {
145150

146151
// GetCmdGetContractState dumps full internal state of a given contract
147152
func GetCmdGetContractState(cdc *codec.Codec) *cobra.Command {
153+
cmd := &cobra.Command{
154+
Use: "contract-state",
155+
Short: "Querying commands for the wasm module",
156+
DisableFlagParsing: true,
157+
SuggestionsMinimumDistance: 2,
158+
RunE: client.ValidateCmd,
159+
}
160+
cmd.AddCommand(client.GetCommands(
161+
GetCmdGetContractStateAll(cdc),
162+
GetCmdGetContractStateRaw(cdc),
163+
GetCmdGetContractStateSmart(cdc),
164+
)...)
165+
return cmd
166+
167+
}
168+
169+
func GetCmdGetContractStateAll(cdc *codec.Codec) *cobra.Command {
148170
return &cobra.Command{
149-
Use: "contract-state [bech32_address]",
150-
Short: "Prints out internal state of a contract given its address",
151-
Long: "Prints out internal state of a contract given its address",
171+
Use: "all [bech32_address]",
172+
Short: "Prints out all internal state of a contract given its address",
173+
Long: "Prints out all internal state of a contract given its address",
152174
Args: cobra.ExactArgs(1),
153175
RunE: func(cmd *cobra.Command, args []string) error {
154176
cliCtx := context.NewCLIContext().WithCodec(cdc)
@@ -158,7 +180,7 @@ func GetCmdGetContractState(cdc *codec.Codec) *cobra.Command {
158180
return err
159181
}
160182

161-
route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String())
183+
route := fmt.Sprintf("custom/%s/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String(), keeper.QueryMethodContractStateAll)
162184
res, _, err := cliCtx.Query(route)
163185
if err != nil {
164186
return err
@@ -168,3 +190,114 @@ func GetCmdGetContractState(cdc *codec.Codec) *cobra.Command {
168190
},
169191
}
170192
}
193+
194+
func GetCmdGetContractStateRaw(cdc *codec.Codec) *cobra.Command {
195+
decoder := newArgDecoder(hex.DecodeString)
196+
cmd := &cobra.Command{
197+
Use: "raw [bech32_address] [key]",
198+
Short: "Prints out internal state for key of a contract given its address",
199+
Long: "Prints out internal state for of a contract given its address",
200+
Args: cobra.ExactArgs(2),
201+
RunE: func(_ *cobra.Command, args []string) error {
202+
cliCtx := context.NewCLIContext().WithCodec(cdc)
203+
204+
addr, err := sdk.AccAddressFromBech32(args[0])
205+
if err != nil {
206+
return err
207+
}
208+
queryData, err := decoder.DecodeString(args[1])
209+
if err != nil {
210+
return err
211+
}
212+
route := fmt.Sprintf("custom/%s/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String(), keeper.QueryMethodContractStateRaw)
213+
res, _, err := cliCtx.QueryWithData(route, queryData)
214+
if err != nil {
215+
return err
216+
}
217+
fmt.Println(string(res))
218+
return nil
219+
},
220+
}
221+
decoder.RegisterFlags(cmd.PersistentFlags(), "key argument")
222+
return cmd
223+
}
224+
225+
func GetCmdGetContractStateSmart(cdc *codec.Codec) *cobra.Command {
226+
decoder := newArgDecoder(asciiDecodeString)
227+
228+
cmd := &cobra.Command{
229+
Use: "smart [bech32_address] [query]",
230+
Short: "Calls contract with given address with query data and prints the returned result",
231+
Long: "Calls contract with given address with query data and prints the returned result",
232+
Args: cobra.ExactArgs(2),
233+
RunE: func(_ *cobra.Command, args []string) error {
234+
cliCtx := context.NewCLIContext().WithCodec(cdc)
235+
236+
addr, err := sdk.AccAddressFromBech32(args[0])
237+
if err != nil {
238+
return err
239+
}
240+
key := args[1]
241+
if key == "" {
242+
return errors.New("key must not be empty")
243+
}
244+
route := fmt.Sprintf("custom/%s/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String(), keeper.QueryMethodContractStateSmart)
245+
246+
queryData, err := decoder.DecodeString(args[1])
247+
if err != nil {
248+
return fmt.Errorf("decode query: %s", err)
249+
}
250+
res, _, err := cliCtx.QueryWithData(route, queryData)
251+
if err != nil {
252+
return err
253+
}
254+
fmt.Println(string(res))
255+
return nil
256+
},
257+
}
258+
decoder.RegisterFlags(cmd.PersistentFlags(), "query argument")
259+
return cmd
260+
}
261+
262+
type argumentDecoder struct {
263+
// dec is the default decoder
264+
dec func(string) ([]byte, error)
265+
asciiF, hexF, b64F bool
266+
}
267+
268+
func newArgDecoder(def func(string) ([]byte, error)) *argumentDecoder {
269+
return &argumentDecoder{dec: def}
270+
}
271+
272+
func (a *argumentDecoder) RegisterFlags(f *flag.FlagSet, argName string) {
273+
f.BoolVar(&a.asciiF, "ascii", false, "ascii encoded "+argName)
274+
f.BoolVar(&a.hexF, "hex", false, "hex encoded "+argName)
275+
f.BoolVar(&a.b64F, "b64", false, "base64 encoded "+argName)
276+
}
277+
278+
func (a *argumentDecoder) DecodeString(s string) ([]byte, error) {
279+
found := -1
280+
for i, v := range []*bool{&a.asciiF, &a.hexF, &a.b64F} {
281+
if !*v {
282+
continue
283+
}
284+
if found != -1 {
285+
return nil, errors.New("multiple decoding flags used")
286+
}
287+
found = i
288+
}
289+
switch found {
290+
case 0:
291+
return asciiDecodeString(s)
292+
case 1:
293+
return hex.DecodeString(s)
294+
case 2:
295+
return base64.StdEncoding.DecodeString(s)
296+
default:
297+
return a.dec(s)
298+
}
299+
}
300+
301+
func asciiDecodeString(s string) ([]byte, error) {
302+
return []byte(s), nil
303+
}

x/wasm/internal/keeper/keeper.go

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const GasMultiplier = 100
2727
// MaxGas for a contract is 900 million (enforced in rust)
2828
const MaxGas = 900_000_000
2929

30+
const smartQueryGasLimit = 3000000 // Todo: should be set by app.toml
31+
3032
// Keeper will have a reference to Wasmer with it's own data directory.
3133
type Keeper struct {
3234
storeKey sdk.StoreKey
@@ -37,6 +39,8 @@ type Keeper struct {
3739
router sdk.Router
3840

3941
wasmer wasm.Wasmer
42+
// queryGasLimit is the max wasm gas that can be spent on executing a query with a contract
43+
queryGasLimit uint64
4044
}
4145

4246
// NewKeeper creates a new contract Keeper instance
@@ -53,6 +57,7 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, accountKeeper auth.Accou
5357
accountKeeper: accountKeeper,
5458
bankKeeper: bankKeeper,
5559
router: router,
60+
queryGasLimit: smartQueryGasLimit,
5661
}
5762
}
5863

@@ -132,22 +137,10 @@ func (k Keeper) Instantiate(ctx sdk.Context, creator sdk.AccAddress, codeID uint
132137

133138
// Execute executes the contract instance
134139
func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, coins sdk.Coins, msgs []byte) (sdk.Result, sdk.Error) {
135-
store := ctx.KVStore(k.storeKey)
136-
137-
contractBz := store.Get(types.GetContractAddressKey(contractAddress))
138-
if contractBz == nil {
139-
return sdk.Result{}, types.ErrNotFound("contract")
140-
}
141-
var contract types.ContractInfo
142-
k.cdc.MustUnmarshalBinaryBare(contractBz, &contract)
143-
144-
contractInfoBz := store.Get(types.GetCodeKey(contract.CodeID))
145-
if contractInfoBz == nil {
146-
return sdk.Result{}, types.ErrNotFound("contract info")
140+
codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress)
141+
if err != nil {
142+
return sdk.Result{}, err
147143
}
148-
var codeInfo types.CodeInfo
149-
k.cdc.MustUnmarshalBinaryBare(contractInfoBz, &codeInfo)
150-
151144
// add more funds
152145
sdkerr := k.bankKeeper.SendCoins(ctx, caller, contractAddress, coins)
153146
if sdkerr != nil {
@@ -156,13 +149,10 @@ func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller
156149
contractAccount := k.accountKeeper.GetAccount(ctx, contractAddress)
157150
params := types.NewParams(ctx, caller, coins, contractAccount)
158151

159-
prefixStoreKey := types.GetContractStorePrefixKey(contractAddress)
160-
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
161-
162152
gas := gasForContract(ctx)
163-
res, err := k.wasmer.Execute(codeInfo.CodeHash, params, msgs, prefixStore, gas)
164-
if err != nil {
165-
return sdk.Result{}, types.ErrExecuteFailed(err)
153+
res, execErr := k.wasmer.Execute(codeInfo.CodeHash, params, msgs, prefixStore, gas)
154+
if execErr != nil {
155+
return sdk.Result{}, types.ErrExecuteFailed(execErr)
166156
}
167157
consumeGas(ctx, res.GasUsed)
168158

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

167+
// QuerySmart queries the smart contract itself.
168+
func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]types.Model, sdk.Error) {
169+
ctx = ctx.WithGasMeter(sdk.NewGasMeter(k.queryGasLimit))
170+
171+
codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr)
172+
if err != nil {
173+
return nil, err
174+
}
175+
queryResult, gasUsed, qErr := k.wasmer.Query(codeInfo.CodeHash, req, prefixStore, gasForContract(ctx))
176+
if qErr != nil {
177+
return nil, types.ErrExecuteFailed(qErr)
178+
}
179+
consumeGas(ctx, gasUsed)
180+
models := make([]types.Model, len(queryResult.Results))
181+
for i := range queryResult.Results {
182+
models[i] = types.Model{
183+
Key: queryResult.Results[i].Key,
184+
Value: string(queryResult.Results[i].Value),
185+
}
186+
}
187+
return models, nil
188+
}
189+
190+
// QueryRaw returns the contract's state for give key. For a `nil` key a empty slice` result is returned.
191+
func (k Keeper) QueryRaw(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []types.Model {
192+
result := make([]types.Model, 0)
193+
if key == nil {
194+
return result
195+
}
196+
prefixStoreKey := types.GetContractStorePrefixKey(contractAddress)
197+
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
198+
199+
if val := prefixStore.Get(key); val != nil {
200+
return append(result, types.Model{
201+
Key: string(key),
202+
Value: string(val),
203+
})
204+
}
205+
return result
206+
}
207+
208+
func (k Keeper) contractInstance(ctx sdk.Context, contractAddress sdk.AccAddress) (types.CodeInfo, prefix.Store, sdk.Error) {
209+
store := ctx.KVStore(k.storeKey)
210+
211+
contractBz := store.Get(types.GetContractAddressKey(contractAddress))
212+
if contractBz == nil {
213+
return types.CodeInfo{}, prefix.Store{}, types.ErrNotFound("contract")
214+
}
215+
var contract types.ContractInfo
216+
k.cdc.MustUnmarshalBinaryBare(contractBz, &contract)
217+
218+
contractInfoBz := store.Get(types.GetCodeKey(contract.CodeID))
219+
if contractInfoBz == nil {
220+
return types.CodeInfo{}, prefix.Store{}, types.ErrNotFound("contract info")
221+
}
222+
var codeInfo types.CodeInfo
223+
k.cdc.MustUnmarshalBinaryBare(contractInfoBz, &codeInfo)
224+
prefixStoreKey := types.GetContractStorePrefixKey(contractAddress)
225+
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
226+
return codeInfo, prefixStore, nil
227+
}
228+
177229
func (k Keeper) GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo {
178230
store := ctx.KVStore(k.storeKey)
179231
var contract types.ContractInfo

0 commit comments

Comments
 (0)