commit
b516ffb31f
@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { Random } from "@iov/crypto";
|
||||
import { Random, Sha256 } from "@iov/crypto";
|
||||
import { Bech32, Encoding } from "@iov/encoding";
|
||||
|
||||
import { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
|
||||
@ -85,13 +85,17 @@ function makeRandomAddress(): string {
|
||||
return Bech32.encode("cosmos", Random.getBytes(20));
|
||||
}
|
||||
|
||||
async function uploadContract(client: RestClient, pen: Pen): Promise<PostTxsResponse> {
|
||||
async function uploadCustomContract(
|
||||
client: RestClient,
|
||||
pen: Pen,
|
||||
wasmCode: Uint8Array,
|
||||
): Promise<PostTxsResponse> {
|
||||
const memo = "My first contract on chain";
|
||||
const theMsg: MsgStoreCode = {
|
||||
type: "wasm/store-code",
|
||||
value: {
|
||||
sender: faucet.address,
|
||||
wasm_byte_code: toBase64(getRandomizedContract()),
|
||||
wasm_byte_code: toBase64(wasmCode),
|
||||
source: "https://github.com/confio/cosmwasm/raw/0.7/lib/vm/testdata/contract_0.6.wasm",
|
||||
builder: "cosmwasm-opt:0.6.2",
|
||||
},
|
||||
@ -113,6 +117,10 @@ async function uploadContract(client: RestClient, pen: Pen): Promise<PostTxsResp
|
||||
return client.postTx(marshalTx(signedTx));
|
||||
}
|
||||
|
||||
async function uploadContract(client: RestClient, pen: Pen): Promise<PostTxsResponse> {
|
||||
return uploadCustomContract(client, pen, getRandomizedContract());
|
||||
}
|
||||
|
||||
async function instantiateContract(
|
||||
client: RestClient,
|
||||
pen: Pen,
|
||||
@ -223,6 +231,7 @@ describe("RestClient", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// this is failing for me on first run (faucet has not signed anything)
|
||||
it("has correct pubkey for faucet", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const client = new RestClient(httpUrl);
|
||||
@ -362,7 +371,8 @@ describe("RestClient", () => {
|
||||
const numExisting = existingInfos.length;
|
||||
|
||||
// upload data
|
||||
const result = await uploadContract(client, pen);
|
||||
const wasmCode = getRandomizedContract();
|
||||
const result = await uploadCustomContract(client, pen, wasmCode);
|
||||
expect(result.code).toBeFalsy();
|
||||
const logs = parseLogs(result.logs);
|
||||
const codeIdAttr = findAttribute(logs, "message", "code_id");
|
||||
@ -375,10 +385,19 @@ describe("RestClient", () => {
|
||||
expect(lastInfo.id).toEqual(codeId);
|
||||
expect(lastInfo.creator).toEqual(faucet.address);
|
||||
|
||||
// TODO: check code hash matches expectation
|
||||
// expect(lastInfo.code_hash).toEqual(faucet.address);
|
||||
// ensure metadata is present
|
||||
expect(lastInfo.source).toEqual(
|
||||
"https://github.com/confio/cosmwasm/raw/0.7/lib/vm/testdata/contract_0.6.wasm",
|
||||
);
|
||||
expect(lastInfo.builder).toEqual("cosmwasm-opt:0.6.2");
|
||||
|
||||
// TODO: download code and check against auto-gen
|
||||
// check code hash matches expectation
|
||||
const wasmHash = new Sha256(wasmCode).digest();
|
||||
expect(lastInfo.code_hash.toLowerCase()).toEqual(toHex(wasmHash));
|
||||
|
||||
// download code and check against auto-gen
|
||||
const download = await client.getCode(codeId);
|
||||
expect(download).toEqual(wasmCode);
|
||||
});
|
||||
|
||||
it("can list contracts and get info", async () => {
|
||||
@ -408,6 +427,8 @@ describe("RestClient", () => {
|
||||
|
||||
// create new instance and compare before and after
|
||||
const existingContracts = await client.listContractAddresses();
|
||||
const existingContractsByCode = await client.listContractsByCodeId(codeId);
|
||||
existingContractsByCode.forEach(ctc => expect(ctc.code_id).toEqual(codeId));
|
||||
|
||||
const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount);
|
||||
expect(result.code).toBeFalsy();
|
||||
@ -424,6 +445,11 @@ describe("RestClient", () => {
|
||||
const lastContract = diff[0];
|
||||
expect(lastContract).toEqual(myAddress);
|
||||
|
||||
// also by codeID list
|
||||
const newContractsByCode = await client.listContractsByCodeId(codeId);
|
||||
newContractsByCode.forEach(ctc => expect(ctc.code_id).toEqual(codeId));
|
||||
expect(newContractsByCode.length).toEqual(existingContractsByCode.length + 1);
|
||||
|
||||
// check out info
|
||||
const myInfo = await client.getContractInfo(myAddress);
|
||||
expect(myInfo.code_id).toEqual(codeId);
|
||||
@ -501,18 +527,14 @@ describe("RestClient", () => {
|
||||
// invalid query syntax throws an error
|
||||
await client.queryContractSmart(contractAddress, { nosuchkey: {} }).then(
|
||||
() => fail("shouldn't succeed"),
|
||||
error => expect(error).toBeTruthy(),
|
||||
error => expect(error).toMatch("Error parsing QueryMsg"),
|
||||
);
|
||||
// TODO: debug rest server. I expect a 'Parse Error', but get
|
||||
// Request failed with status code 500 to match 'Parse Error:'
|
||||
|
||||
// invalid address throws an error
|
||||
await client.queryContractSmart(noContract, { verifier: {} }).then(
|
||||
() => fail("shouldn't succeed"),
|
||||
error => expect(error).toBeTruthy(),
|
||||
error => expect(error).toMatch("not found"),
|
||||
);
|
||||
// TODO: debug rest server. I expect a 'not found', but get
|
||||
// Request failed with status code 500 to match 'Parse Error:'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Encoding } from "@iov/encoding";
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
import axios, { AxiosError, AxiosInstance } from "axios";
|
||||
|
||||
import { AminoTx, CodeInfo, ContractInfo, CosmosSdkAccount, isAminoStdTx, StdTx, WasmData } from "./types";
|
||||
|
||||
@ -45,11 +45,11 @@ interface AuthAccountsResponse {
|
||||
// Currently all wasm query responses return json-encoded strings...
|
||||
// later deprecate this and use the specific types for result
|
||||
// (assuming it is inlined, no second parse needed)
|
||||
type WasmResponse = WasmSuccess | WasmError;
|
||||
type WasmResponse<T = string> = WasmSuccess<T> | WasmError;
|
||||
|
||||
interface WasmSuccess {
|
||||
interface WasmSuccess<T = string> {
|
||||
readonly height: string;
|
||||
readonly result: string;
|
||||
readonly result: T;
|
||||
}
|
||||
|
||||
interface WasmError {
|
||||
@ -92,6 +92,11 @@ interface EncodeTxResponse {
|
||||
readonly tx: string;
|
||||
}
|
||||
|
||||
interface GetCodeResult {
|
||||
// base64 encoded wasm
|
||||
readonly code: string;
|
||||
}
|
||||
|
||||
type RestClientResponse =
|
||||
| NodeInfoResponse
|
||||
| BlocksResponse
|
||||
@ -100,21 +105,49 @@ type RestClientResponse =
|
||||
| SearchTxsResponse
|
||||
| PostTxsResponse
|
||||
| EncodeTxResponse
|
||||
| WasmResponse;
|
||||
| WasmResponse<string>
|
||||
| WasmResponse<GetCodeResult>;
|
||||
|
||||
type BroadcastMode = "block" | "sync" | "async";
|
||||
|
||||
function isWasmError(resp: WasmResponse): resp is WasmError {
|
||||
function isWasmError<T>(resp: WasmResponse<T>): resp is WasmError {
|
||||
return (resp as WasmError).error !== undefined;
|
||||
}
|
||||
|
||||
function parseWasmResponse(response: WasmResponse): any {
|
||||
function unwrapWasmResponse<T>(response: WasmResponse<T>): T {
|
||||
if (isWasmError(response)) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return response.result;
|
||||
}
|
||||
|
||||
function parseWasmResponse(response: WasmResponse<string>): any {
|
||||
if (isWasmError(response)) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return JSON.parse(response.result);
|
||||
}
|
||||
|
||||
// We want to get message data from 500 errors
|
||||
// https://stackoverflow.com/questions/56577124/how-to-handle-500-error-message-with-axios
|
||||
// this should be chained to catch one error and throw a more informative one
|
||||
function parseAxios500error(err: AxiosError): never {
|
||||
// use the error message sent from server, not default 500 msg
|
||||
if (err.response?.data) {
|
||||
const data = err.response.data;
|
||||
// expect { error: string }, but otherwise dump
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
} else if (typeof data === "string") {
|
||||
throw new Error(data);
|
||||
} else {
|
||||
throw new Error(JSON.stringify(data));
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export class RestClient {
|
||||
private readonly client: AxiosInstance;
|
||||
// From https://cosmos.network/rpc/#/ICS0/post_txs
|
||||
@ -133,7 +166,7 @@ export class RestClient {
|
||||
}
|
||||
|
||||
public async get(path: string): Promise<RestClientResponse> {
|
||||
const { data } = await this.client.get(path);
|
||||
const { data } = await this.client.get(path).catch(parseAxios500error);
|
||||
if (data === null) {
|
||||
throw new Error("Received null response from server");
|
||||
}
|
||||
@ -141,7 +174,7 @@ export class RestClient {
|
||||
}
|
||||
|
||||
public async post(path: string, params: PostTxsParams): Promise<RestClientResponse> {
|
||||
const { data } = await this.client.post(path, params);
|
||||
const { data } = await this.client.post(path, params).catch(parseAxios500error);
|
||||
if (data === null) {
|
||||
throw new Error("Received null response from server");
|
||||
}
|
||||
@ -237,10 +270,9 @@ export class RestClient {
|
||||
// this will download the original wasm bytecode by code id
|
||||
// throws error if no code with this id
|
||||
public async getCode(id: number): Promise<Uint8Array> {
|
||||
// TODO: broken currently
|
||||
const path = `/wasm/code/${id}`;
|
||||
const responseData = await this.get(path);
|
||||
const { code } = parseWasmResponse(responseData as WasmResponse);
|
||||
const responseData = (await this.get(path)) as WasmResponse<GetCodeResult>;
|
||||
const { code } = unwrapWasmResponse(responseData);
|
||||
return fromBase64(code);
|
||||
}
|
||||
|
||||
@ -252,6 +284,14 @@ export class RestClient {
|
||||
return addresses || [];
|
||||
}
|
||||
|
||||
public async listContractsByCodeId(id: number): Promise<readonly ContractInfo[]> {
|
||||
const path = `/wasm/code/${id}/contracts`;
|
||||
const responseData = await this.get(path);
|
||||
// answer may be null (go's encoding of empty array)
|
||||
const contracts: ContractInfo[] | null = parseWasmResponse(responseData as WasmResponse);
|
||||
return contracts || [];
|
||||
}
|
||||
|
||||
// throws error if no contract at this address
|
||||
public async getContractInfo(address: string): Promise<ContractInfo> {
|
||||
const path = `/wasm/contract/${address}`;
|
||||
|
||||
13
packages/sdk/types/restclient.d.ts
vendored
13
packages/sdk/types/restclient.d.ts
vendored
@ -30,10 +30,10 @@ interface AuthAccountsResponse {
|
||||
readonly value: CosmosSdkAccount;
|
||||
};
|
||||
}
|
||||
declare type WasmResponse = WasmSuccess | WasmError;
|
||||
interface WasmSuccess {
|
||||
declare type WasmResponse<T = string> = WasmSuccess<T> | WasmError;
|
||||
interface WasmSuccess<T = string> {
|
||||
readonly height: string;
|
||||
readonly result: string;
|
||||
readonly result: T;
|
||||
}
|
||||
interface WasmError {
|
||||
readonly error: string;
|
||||
@ -68,6 +68,9 @@ export interface PostTxsResponse {
|
||||
interface EncodeTxResponse {
|
||||
readonly tx: string;
|
||||
}
|
||||
interface GetCodeResult {
|
||||
readonly code: string;
|
||||
}
|
||||
declare type RestClientResponse =
|
||||
| NodeInfoResponse
|
||||
| BlocksResponse
|
||||
@ -76,7 +79,8 @@ declare type RestClientResponse =
|
||||
| SearchTxsResponse
|
||||
| PostTxsResponse
|
||||
| EncodeTxResponse
|
||||
| WasmResponse;
|
||||
| WasmResponse<string>
|
||||
| WasmResponse<GetCodeResult>;
|
||||
declare type BroadcastMode = "block" | "sync" | "async";
|
||||
export declare class RestClient {
|
||||
private readonly client;
|
||||
@ -96,6 +100,7 @@ export declare class RestClient {
|
||||
listCodeInfo(): Promise<readonly CodeInfo[]>;
|
||||
getCode(id: number): Promise<Uint8Array>;
|
||||
listContractAddresses(): Promise<readonly string[]>;
|
||||
listContractsByCodeId(id: number): Promise<readonly ContractInfo[]>;
|
||||
getContractInfo(address: string): Promise<ContractInfo>;
|
||||
getAllContractState(address: string): Promise<readonly WasmData[]>;
|
||||
queryContractRaw(address: string, key: Uint8Array): Promise<unknown | null>;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Choose from https://hub.docker.com/r/cosmwasm/wasmd-demo/tags
|
||||
REPOSITORY="cosmwasm/wasmd-demo"
|
||||
VERSION="v0.0.2"
|
||||
VERSION="v0.0.4"
|
||||
|
||||
CONTAINER_NAME="wasmd"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user