From 520f32ed3687608f679b2ab7ae4fc3ab0d0521ae Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 13 Feb 2020 16:24:17 +0100 Subject: [PATCH 1/4] Add CosmWasmClient.queryContractRaw --- packages/sdk/src/cosmwasmclient.spec.ts | 64 +++++++++++++++++++++++++ packages/sdk/src/cosmwasmclient.ts | 13 +++++ packages/sdk/types/cosmwasmclient.d.ts | 7 +++ 3 files changed, 84 insertions(+) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 5862ae17..01dce878 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -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 { 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,57 @@ 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 with address ${nonExistentAddress}`), + ); + }); + }); }); diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index c94232fd..8645e845 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -294,4 +294,17 @@ 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 { + // just test contract existence + const _info = await this.restClient.getContractInfo(address); + + return this.restClient.queryContractRaw(address, key); + } } diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index 14be80a8..81c2fecc 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -62,4 +62,11 @@ export declare class CosmWasmClient { memo?: string, transferAmount?: readonly Coin[], ): Promise; + /** + * 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; } From 62d6bd7bd99bdf5208f6661c015a3e4c0a2d08ee Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 13 Feb 2020 23:02:22 +0100 Subject: [PATCH 2/4] Improve error messages --- packages/sdk/src/cosmwasmclient.spec.ts | 2 +- packages/sdk/src/restclient.spec.ts | 5 +++-- packages/sdk/src/restclient.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 01dce878..287a0252 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -419,7 +419,7 @@ describe("CosmWasmClient", () => { const client = CosmWasmClient.makeReadOnly(httpUrl); await client.queryContractRaw(nonExistentAddress, configKey).then( () => fail("must not succeed"), - error => expect(error).toMatch(`No contract with address ${nonExistentAddress}`), + error => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`), ); }); }); diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 276de57b..74f53a22 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -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", () => { diff --git a/packages/sdk/src/restclient.ts b/packages/sdk/src/restclient.ts index a8d4b1a5..3fd3c847 100644 --- a/packages/sdk/src/restclient.ts +++ b/packages/sdk/src/restclient.ts @@ -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; } From 91a3144e15ea849dbb2f308e662c0286c04696ad Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 13 Feb 2020 23:06:37 +0100 Subject: [PATCH 3/4] Add CosmWasmClient.queryContractSmart --- packages/sdk/src/cosmwasmclient.spec.ts | 49 ++++++++++++++++++++++++- packages/sdk/src/cosmwasmclient.ts | 22 +++++++++++ packages/sdk/types/cosmwasmclient.d.ts | 7 ++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 287a0252..cab410d3 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -10,7 +10,7 @@ import cosmoshub from "./testdata/cosmoshub.json"; import { getRandomizedHackatom, makeRandomAddress } from "./testutils.spec"; import { Coin, CosmosSdkTx, MsgSend, StdFee } from "./types"; -const { fromUtf8, toAscii } = Encoding; +const { fromAscii, fromUtf8, toAscii } = Encoding; const httpUrl = "http://localhost:1317"; @@ -423,4 +423,51 @@ describe("CosmWasmClient", () => { ); }); }); + + 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}"`), + ); + }); + }); }); diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index 8645e845..1079dd85 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -307,4 +307,26 @@ export class CosmWasmClient { 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 { + 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; + } + } + } } diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index 81c2fecc..88eebd4c 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -69,4 +69,11 @@ export declare class CosmWasmClient { * Promise is rejected when contract does not exist. */ queryContractRaw(address: string, key: Uint8Array): Promise; + /** + * 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; } From b56970eeb9243d9a4d464df8172ab7aed4792264 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 13 Feb 2020 23:08:37 +0100 Subject: [PATCH 4/4] Use CosmWasmClient.queryContractSmart --- packages/bcp/src/cosmwasmconnection.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index d8be0c4c..2dcf49a4 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -160,8 +160,7 @@ export class CosmWasmConnection implements BlockchainConnection { this.erc20Tokens.map( async (erc20): Promise => { 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 {