From 477511f1b32554f6adb8a2d0e74f2936d41d562a Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 5 Feb 2020 16:11:46 +0100 Subject: [PATCH] Complete wasm calls via manual testing with curl --- packages/sdk/src/restclient.ts | 102 ++++++++++++++++++++++----------- packages/sdk/src/types.ts | 14 ++++- 2 files changed, 80 insertions(+), 36 deletions(-) diff --git a/packages/sdk/src/restclient.ts b/packages/sdk/src/restclient.ts index 9782f198..8f121628 100644 --- a/packages/sdk/src/restclient.ts +++ b/packages/sdk/src/restclient.ts @@ -1,9 +1,9 @@ import { Encoding } from "@iov/encoding"; import axios, { AxiosInstance } from "axios"; -import { AminoTx, BaseAccount, CodeInfo, CodeInfoWithId, ContractInfo, isAminoStdTx, StdTx } from "./types"; +import { AminoTx, BaseAccount, CodeInfo, ContractInfo, isAminoStdTx, StdTx, WasmData } from "./types"; -const { fromUtf8 } = Encoding; +const { fromBase64, fromUtf8, toHex, toUtf8 } = Encoding; interface NodeInfo { readonly network: string; @@ -42,31 +42,17 @@ interface AuthAccountsResponse { } // Currently all wasm query responses return json-encoded strings... -// later deprecate this and use the specific types below -interface WasmResponse { +// later deprecate this and use the specific types for result +// (assuming it is inlined, no second parse needed) +type WasmResponse = WasmSuccess | WasmError; + +interface WasmSuccess { + readonly height: string; readonly result: string; } -interface ListCodeResponse { - // TODO: this is returning json.encoded string now, parse on client or server? - readonly result: readonly CodeInfoWithId[]; -} - -interface GetCodeResponse { - // TODO: this is returning json.encoded string now, parse on client or server? - readonly result: CodeInfo; -} - -interface ListContractResponse { - // TODO: this is returning json.encoded string now, parse on client or server? - // contains list of all contract addresses - readonly result: readonly string[]; -} - -interface GetContractResponse { - // TODO: this is returning json.encoded string now, parse on client or server? - // contains list of all contract addresses - readonly result: ContractInfo; +interface WasmError { + readonly error: string; } export interface TxsResponse { @@ -117,6 +103,17 @@ type RestClientResponse = type BroadcastMode = "block" | "sync" | "async"; +function isWasmError(resp: WasmResponse): resp is WasmError { + return (resp as WasmError).error !== undefined; +} + +function parseWasmResponse(response: WasmResponse): any { + if (isWasmError(response)) { + throw new Error(response.error); + } + return JSON.parse(response.result); +} + export class RestClient { private readonly client: AxiosInstance; // From https://cosmos.network/rpc/#/ICS0/post_txs @@ -229,31 +226,68 @@ export class RestClient { } // wasm rest queries are listed here: https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27 - public async listCodeInfo(): Promise { + public async listCodeInfo(): Promise { const path = `/wasm/code`; const responseData = await this.get(path); - const codes = JSON.parse((responseData as WasmResponse).result); - return codes as CodeInfoWithId[]; + // answer may be null (empty array) + return parseWasmResponse(responseData as WasmResponse) || []; } - public async getCodeInfo(id: number): Promise { + // this will download the original wasm bytecode by code id + // throws error if no code with this id + public async getCode(id: number): Promise { const path = `/wasm/code/${id}`; const responseData = await this.get(path); - const code = JSON.parse((responseData as WasmResponse).result); - return code as CodeInfo; + const { code } = parseWasmResponse(responseData as WasmResponse); + return fromBase64(code); } public async listContractAddresses(): Promise { const path = `/wasm/contract`; const responseData = await this.get(path); - const codes = JSON.parse((responseData as WasmResponse).result); - return codes as string[]; + // answer may be null (empty array) + return parseWasmResponse(responseData as WasmResponse) || []; } + // throws error if no contract at this address public async getContractInfo(address: string): Promise { const path = `/wasm/contract/${address}`; const responseData = await this.get(path); - const code = JSON.parse((responseData as WasmResponse).result); - return code as ContractInfo; + const info = parseWasmResponse(responseData as WasmResponse); + if (!info) { + throw new Error(`No contract with address ${address}`); + } + return info; + } + + // Returns all contract state. + // This is an empty array if no such contract, or contract has no data. + public async getAllContractState(address: string): Promise { + const path = `/wasm/contract/${address}/state`; + const responseData = await this.get(path); + return parseWasmResponse(responseData as WasmResponse); + } + + // Returns the data at the key if present (unknown decoded json), + // or null if no data at this (contract address, key) pair + public async getContractKey(address: string, key: Uint8Array): Promise { + const hexKey = toHex(key); + const path = `/wasm/contract/${address}/raw/${hexKey}?encoding=hex`; + const responseData = await this.get(path); + const data: readonly WasmData[] = parseWasmResponse(responseData as WasmResponse); + return data.length === 0 ? null : data[0].val; + } + + // Makes a "smart query" on the contract, returns response verbatim (json.RawMessage) + // Throws error if no such contract or invalid query format + public async queryContract(address: string, query: object): Promise { + const encoded = toHex(toUtf8(JSON.stringify(query))); + const path = `/wasm/contract/${address}/smart/${encoded}?encoding=hex`; + const responseData = (await this.get(path)) as WasmResponse; + if (isWasmError(responseData)) { + throw new Error(responseData.error); + } + // no extra parse here + return responseData.result; } } diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 62fc2b48..9cf9cdfb 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -164,16 +164,19 @@ export interface BaseAccount { export type NonceInfo = Pick; export interface CodeInfo { + readonly id: number; /** Bech32 account address */ readonly creator: string; /** Hex-encoded sha256 hash of the code stored here */ readonly code_hash: string; + // TODO: these are not supported in current wasmd readonly source?: string; readonly builder?: string; } -export interface CodeInfoWithId extends CodeInfo { - readonly id: number; +export interface CodeDetails { + // TODO: this should be base64 encoded string with content - not in current stack + readonly code: string; } export interface ContractInfo { @@ -183,3 +186,10 @@ export interface ContractInfo { /** Argument passed on initialization of the contract */ readonly init_msg: object; } + +export interface WasmData { + // key is hex-encoded + readonly key: string; + // value can be any decoded json, often an object but can be anything + readonly val: unknown; +}