Merge pull request #87 from confio/CosmWasmClient-queries

Add query support to CosmWasmClient
This commit is contained in:
Simon Warta 2020-02-14 23:04:21 +01:00 committed by GitHub
commit aaab4b89ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 165 additions and 5 deletions

View File

@ -160,8 +160,7 @@ export class CosmWasmConnection implements BlockchainConnection {
this.erc20Tokens.map(
async (erc20): Promise<Amount> => {
const queryMsg = { balance: { address: address } };
// tslint:disable-next-line: deprecation
const smart = await this.restClient.queryContractSmart(erc20.contractAddress, queryMsg);
const smart = await this.cosmWasmClient.queryContractSmart(erc20.contractAddress, queryMsg);
const response = JSON.parse(fromAscii(smart));
const normalizedBalance = new BN(response.balance).toString();
return {

View File

@ -1,3 +1,4 @@
import { Bech32, Encoding } from "@iov/encoding";
import { assert } from "@iov/utils";
import { CosmWasmClient } from "./cosmwasmclient";
@ -9,6 +10,8 @@ import cosmoshub from "./testdata/cosmoshub.json";
import { getRandomizedHackatom, makeRandomAddress } from "./testutils.spec";
import { Coin, CosmosSdkTx, MsgSend, StdFee } from "./types";
const { fromAscii, fromUtf8, toAscii } = Encoding;
const httpUrl = "http://localhost:1317";
function cosmosEnabled(): boolean {
@ -35,6 +38,14 @@ const unusedAccount = {
address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u",
};
interface HackatomInstance {
readonly initMsg: {
readonly verifier: string;
readonly beneficiary: string;
};
readonly address: string;
}
describe("CosmWasmClient", () => {
describe("makeReadOnly", () => {
it("can be constructed", () => {
@ -359,4 +370,104 @@ describe("CosmWasmClient", () => {
expect(contractBalance).toEqual([]);
});
});
describe("queryContractRaw", () => {
const configKey = toAscii("config");
const otherKey = toAscii("this_does_not_exist");
let contract: HackatomInstance | undefined;
beforeAll(async () => {
if (cosmosEnabled()) {
pendingWithoutCosmos();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, signBytes => pen.sign(signBytes));
const codeId = await client.upload(getRandomizedHackatom());
const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() };
const contractAddress = await client.instantiate(codeId, initMsg);
contract = { initMsg: initMsg, address: contractAddress };
}
});
it("can query existing key", async () => {
pendingWithoutCosmos();
assert(contract);
const client = CosmWasmClient.makeReadOnly(httpUrl);
const raw = await client.queryContractRaw(contract.address, configKey);
assert(raw, "must get result");
expect(JSON.parse(fromUtf8(raw))).toEqual({
verifier: Array.from(Bech32.decode(contract.initMsg.verifier).data),
beneficiary: Array.from(Bech32.decode(contract.initMsg.beneficiary).data),
funder: Array.from(Bech32.decode(faucet.address).data),
});
});
it("can query non-existent key", async () => {
pendingWithoutCosmos();
assert(contract);
const client = CosmWasmClient.makeReadOnly(httpUrl);
const raw = await client.queryContractRaw(contract.address, otherKey);
expect(raw).toBeNull();
});
it("errors for non-existent contract", async () => {
pendingWithoutCosmos();
assert(contract);
const nonExistentAddress = makeRandomAddress();
const client = CosmWasmClient.makeReadOnly(httpUrl);
await client.queryContractRaw(nonExistentAddress, configKey).then(
() => fail("must not succeed"),
error => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`),
);
});
});
describe("queryContractSmart", () => {
let contract: HackatomInstance | undefined;
beforeAll(async () => {
if (cosmosEnabled()) {
pendingWithoutCosmos();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, signBytes => pen.sign(signBytes));
const codeId = await client.upload(getRandomizedHackatom());
const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() };
const contractAddress = await client.instantiate(codeId, initMsg);
contract = { initMsg: initMsg, address: contractAddress };
}
});
it("works", async () => {
pendingWithoutCosmos();
assert(contract);
const client = CosmWasmClient.makeReadOnly(httpUrl);
const verifier = await client.queryContractSmart(contract.address, { verifier: {} });
expect(fromAscii(verifier)).toEqual(contract.initMsg.verifier);
});
it("errors for malformed query message", async () => {
pendingWithoutCosmos();
assert(contract);
const client = CosmWasmClient.makeReadOnly(httpUrl);
await client.queryContractSmart(contract.address, { broken: {} }).then(
() => fail("must not succeed"),
error => expect(error).toMatch(/Error parsing QueryMsg/i),
);
});
it("errors for non-existent contract", async () => {
pendingWithoutCosmos();
const nonExistentAddress = makeRandomAddress();
const client = CosmWasmClient.makeReadOnly(httpUrl);
await client.queryContractSmart(nonExistentAddress, { verifier: {} }).then(
() => fail("must not succeed"),
error => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`),
);
});
});
});

View File

@ -294,4 +294,39 @@ export class CosmWasmClient {
logs: result.logs,
};
}
/**
* Returns the data at the key if present (raw contract dependent storage data)
* or null if no data at this key.
*
* Promise is rejected when contract does not exist.
*/
public async queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null> {
// just test contract existence
const _info = await this.restClient.getContractInfo(address);
return this.restClient.queryContractRaw(address, key);
}
/**
* Makes a "smart query" on the contract, returns raw data
*
* Promise is rejected when contract does not exist.
* Promise is rejected for invalid query format.
*/
public async queryContractSmart(address: string, queryMsg: object): Promise<Uint8Array> {
try {
return await this.restClient.queryContractSmart(address, queryMsg);
} catch (error) {
if (error instanceof Error) {
if (error.message === "not found: contract") {
throw new Error(`No contract found at address "${address}"`);
} else {
throw error;
}
} else {
throw error;
}
}
}
}

View File

@ -430,10 +430,11 @@ describe("RestClient", () => {
expect((myInfo.init_msg as any).beneficiary).toEqual(beneficiaryAddress);
// make sure random addresses don't give useful info
const nonExistentAddress = makeRandomAddress();
await client
.getContractInfo(beneficiaryAddress)
.getContractInfo(nonExistentAddress)
.then(() => fail("this shouldn't succeed"))
.catch(error => expect(error).toMatch(`No contract with address ${beneficiaryAddress}`));
.catch(error => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`));
});
describe("contract state", () => {

View File

@ -311,7 +311,7 @@ export class RestClient {
// rest server returns null if no data for the address
const info: ContractInfo | null = parseWasmResponse(responseData as WasmResponse);
if (!info) {
throw new Error(`No contract with address ${address}`);
throw new Error(`No contract found at address "${address}"`);
}
return info;
}

View File

@ -62,4 +62,18 @@ export declare class CosmWasmClient {
memo?: string,
transferAmount?: readonly Coin[],
): Promise<ExecuteResult>;
/**
* Returns the data at the key if present (raw contract dependent storage data)
* or null if no data at this key.
*
* Promise is rejected when contract does not exist.
*/
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
/**
* Makes a "smart query" on the contract, returns raw data
*
* Promise is rejected when contract does not exist.
* Promise is rejected for invalid query format.
*/
queryContractSmart(address: string, queryMsg: object): Promise<Uint8Array>;
}