From 7316838cf24d94700f936d606eabaa3d5e2fda5e Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Sun, 1 Mar 2020 23:07:22 +0100 Subject: [PATCH 01/13] Test tx query by tags --- packages/sdk/src/restclient.spec.ts | 123 ++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 82fa3d66..9469447c 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -23,6 +23,8 @@ import { } from "./testutils.spec"; import { Coin, + isMsgInstantiateContract, + isMsgStoreCode, Msg, MsgExecuteContract, MsgInstantiateContract, @@ -51,6 +53,12 @@ const unusedAccount = { address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u", }; +const deployedErc20 = { + codeId: 1, + source: "", + builder: "", +}; + function makeSignedTx(firstMsg: Msg, fee: StdFee, memo: string, firstSignature: StdSignature): StdTx { return { msg: [firstMsg], @@ -479,6 +487,121 @@ describe("RestClient", () => { expect(count).toEqual("0"); } }); + + it("can query by tags (module + code_id)", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(httpUrl); + const result = await client.txsQuery(`message.module=wasm&message.code_id=${deployedErc20.codeId}`); + expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(4); + + // Check first 4 results + const [store, hash, isa, jade] = result.txs + .map(txResponse => txResponse.tx.value.msg) + .map(msgs => { + assert(msgs.length === 1, "Single message transactions expected"); + return msgs[0]; + }); + assert(isMsgStoreCode(store)); + assert(isMsgInstantiateContract(hash)); + assert(isMsgInstantiateContract(isa)); + assert(isMsgInstantiateContract(jade)); + + expect(store.value).toEqual( + jasmine.objectContaining({ + sender: faucet.address, + source: deployedErc20.source, + builder: deployedErc20.builder, + }), + ); + expect(hash.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ + symbol: "HASH", + }), + label: "HASH", + sender: faucet.address, + }); + expect(isa.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ symbol: "ISA" }), + label: "ISA", + sender: faucet.address, + }); + expect(jade.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ symbol: "JADE" }), + label: "JADE", + sender: faucet.address, + }); + }); + + // Like previous test but filtered by message.action=store-code and message.action=instantiate + it("can query by tags (module + code_id + action)", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(httpUrl); + + { + const uploads = await client.txsQuery( + `message.module=wasm&message.code_id=${deployedErc20.codeId}&message.action=store-code`, + ); + expect(parseInt(uploads.count, 10)).toEqual(1); + const msgs = uploads.txs[0].tx.value.msg; + assert(msgs.length === 1, "Single message transactions expected"); + const store = msgs[0]; + assert(isMsgStoreCode(store)); + expect(store.value).toEqual( + jasmine.objectContaining({ + sender: faucet.address, + source: deployedErc20.source, + builder: deployedErc20.builder, + }), + ); + } + + { + const instantiations = await client.txsQuery( + `message.module=wasm&message.code_id=${deployedErc20.codeId}&message.action=instantiate`, + ); + expect(parseInt(instantiations.count, 10)).toBeGreaterThanOrEqual(3); + const [hash, isa, jade] = instantiations.txs + .map(txResponse => txResponse.tx.value.msg) + .map(msgs => { + assert(msgs.length === 1, "Single message transactions expected"); + return msgs[0]; + }); + assert(isMsgInstantiateContract(hash)); + assert(isMsgInstantiateContract(isa)); + assert(isMsgInstantiateContract(jade)); + expect(hash.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ + symbol: "HASH", + }), + label: "HASH", + sender: faucet.address, + }); + expect(isa.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ symbol: "ISA" }), + label: "ISA", + sender: faucet.address, + }); + expect(jade.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ symbol: "JADE" }), + label: "JADE", + sender: faucet.address, + }); + } + }); }); describe("encodeTx", () => { From ea19f9b5e100ef03920d37a78fcf99b576321e2d Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Sun, 1 Mar 2020 23:18:46 +0100 Subject: [PATCH 02/13] Set code meta to ERC20 contract --- packages/sdk/src/cosmwasmclient.spec.ts | 17 +++++++++-------- packages/sdk/src/restclient.spec.ts | 7 +------ packages/sdk/src/testutils.spec.ts | 8 ++++++++ scripts/wasmd/deploy_erc20.js | 7 ++++++- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 5f130e65..5a56e7a7 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -12,6 +12,7 @@ import { RestClient } from "./restclient"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import cosmoshub from "./testdata/cosmoshub.json"; import { + deployedErc20, getRandomizedHackatom, makeRandomAddress, pendingWithoutWasmd, @@ -405,10 +406,10 @@ describe("CosmWasmClient", () => { expect(result.length).toBeGreaterThanOrEqual(1); const [first] = result; expect(first).toEqual({ - id: 1, - checksum: "aff8c8873d79d2153a8b9066a0683fec3c903669267eb806ffa831dcd4b3daae", - source: undefined, - builder: undefined, + id: deployedErc20.codeId, + source: deployedErc20.source, + builder: deployedErc20.builder, + checksum: deployedErc20.checksum, creator: faucet.address, }); }); @@ -421,10 +422,10 @@ describe("CosmWasmClient", () => { const result = await client.getCodeDetails(1); const expectedInfo: Code = { - id: 1, - checksum: "aff8c8873d79d2153a8b9066a0683fec3c903669267eb806ffa831dcd4b3daae", - source: undefined, - builder: undefined, + id: deployedErc20.codeId, + source: deployedErc20.source, + builder: deployedErc20.builder, + checksum: deployedErc20.checksum, creator: faucet.address, }; diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 9469447c..095ab1ba 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -13,6 +13,7 @@ import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import cosmoshub from "./testdata/cosmoshub.json"; import { bech32AddressMatcher, + deployedErc20, getRandomizedHackatom, makeRandomAddress, pendingWithoutWasmd, @@ -53,12 +54,6 @@ const unusedAccount = { address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u", }; -const deployedErc20 = { - codeId: 1, - source: "", - builder: "", -}; - function makeSignedTx(firstMsg: Msg, fee: StdFee, memo: string, firstSignature: StdSignature): StdTx { return { msg: [firstMsg], diff --git a/packages/sdk/src/testutils.spec.ts b/packages/sdk/src/testutils.spec.ts index f72d4636..e1932fa6 100644 --- a/packages/sdk/src/testutils.spec.ts +++ b/packages/sdk/src/testutils.spec.ts @@ -63,6 +63,14 @@ export const tendermintAddressMatcher = /^[0-9A-F]{40}$/; // https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 export const bech32AddressMatcher = /^[\x21-\x7e]{1,83}1[02-9ac-hj-np-z]{38}$/; +/** Deployed as part of scripts/wasmd/init.sh */ +export const deployedErc20 = { + codeId: 1, + source: "https://crates.io/api/v1/crates/cw-erc20/0.2.0/download", + builder: "confio/cosmwasm-opt:0.7.0", + checksum: "aff8c8873d79d2153a8b9066a0683fec3c903669267eb806ffa831dcd4b3daae", +}; + export function wasmdEnabled(): boolean { return !!process.env.WASMD_ENABLED; } diff --git a/scripts/wasmd/deploy_erc20.js b/scripts/wasmd/deploy_erc20.js index 4401d53d..63af5032 100755 --- a/scripts/wasmd/deploy_erc20.js +++ b/scripts/wasmd/deploy_erc20.js @@ -17,6 +17,11 @@ const guest = { address: "cosmos17d0jcz59jf68g52vq38tuuncmwwjk42u6mcxej", }; +const codeMeta = { + source: "https://crates.io/api/v1/crates/cw-erc20/0.2.0/download", + builder: "confio/cosmwasm-opt:0.7.0", +}; + const initMsgHash = { decimals: 5, name: "Hash token", @@ -72,7 +77,7 @@ async function main() { const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); const wasm = fs.readFileSync(__dirname + "/contracts/cw-erc20.wasm"); - const uploadReceipt = await client.upload(wasm, {}, "Upload ERC20 contract"); + const uploadReceipt = await client.upload(wasm, codeMeta, "Upload ERC20 contract"); console.info(`Upload succeeded. Receipt: ${JSON.stringify(uploadReceipt)}`); for (const initMsg of [initMsgHash, initMsgIsa, initMsgJade]) { From ca625c1f3e3358315b1af9fb72bf9fcf95bb7f9d Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 08:22:44 +0100 Subject: [PATCH 03/13] Pull out fromOneElementArray --- packages/sdk/src/restclient.spec.ts | 20 ++++---------------- packages/sdk/src/testutils.spec.ts | 6 ++++++ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 095ab1ba..03b95e8e 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -14,6 +14,7 @@ import cosmoshub from "./testdata/cosmoshub.json"; import { bech32AddressMatcher, deployedErc20, + fromOneElementArray, getRandomizedHackatom, makeRandomAddress, pendingWithoutWasmd, @@ -491,17 +492,11 @@ describe("RestClient", () => { expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(4); // Check first 4 results - const [store, hash, isa, jade] = result.txs - .map(txResponse => txResponse.tx.value.msg) - .map(msgs => { - assert(msgs.length === 1, "Single message transactions expected"); - return msgs[0]; - }); + const [store, hash, isa, jade] = result.txs.map(tx => fromOneElementArray(tx.tx.value.msg)); assert(isMsgStoreCode(store)); assert(isMsgInstantiateContract(hash)); assert(isMsgInstantiateContract(isa)); assert(isMsgInstantiateContract(jade)); - expect(store.value).toEqual( jasmine.objectContaining({ sender: faucet.address, @@ -545,9 +540,7 @@ describe("RestClient", () => { `message.module=wasm&message.code_id=${deployedErc20.codeId}&message.action=store-code`, ); expect(parseInt(uploads.count, 10)).toEqual(1); - const msgs = uploads.txs[0].tx.value.msg; - assert(msgs.length === 1, "Single message transactions expected"); - const store = msgs[0]; + const store = fromOneElementArray(uploads.txs[0].tx.value.msg); assert(isMsgStoreCode(store)); expect(store.value).toEqual( jasmine.objectContaining({ @@ -563,12 +556,7 @@ describe("RestClient", () => { `message.module=wasm&message.code_id=${deployedErc20.codeId}&message.action=instantiate`, ); expect(parseInt(instantiations.count, 10)).toBeGreaterThanOrEqual(3); - const [hash, isa, jade] = instantiations.txs - .map(txResponse => txResponse.tx.value.msg) - .map(msgs => { - assert(msgs.length === 1, "Single message transactions expected"); - return msgs[0]; - }); + const [hash, isa, jade] = instantiations.txs.map(tx => fromOneElementArray(tx.tx.value.msg)); assert(isMsgInstantiateContract(hash)); assert(isMsgInstantiateContract(isa)); assert(isMsgInstantiateContract(jade)); diff --git a/packages/sdk/src/testutils.spec.ts b/packages/sdk/src/testutils.spec.ts index e1932fa6..d072842f 100644 --- a/packages/sdk/src/testutils.spec.ts +++ b/packages/sdk/src/testutils.spec.ts @@ -81,6 +81,12 @@ export function pendingWithoutWasmd(): void { } } +/** Returns first element. Throws if array has a different length than 1. */ +export function fromOneElementArray(elements: ArrayLike): T { + if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`); + return elements[0]; +} + describe("leb128", () => { describe("leb128Encode", () => { it("works for single byte values", () => { From dd8d63053266ae8cc5501cf5c281e6a491bbcb32 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 09:40:27 +0100 Subject: [PATCH 04/13] Improve sentFromOrTo search tests --- packages/sdk/src/cosmwasmclient.spec.ts | 37 ++++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 5a56e7a7..96b7cd44 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -13,13 +13,14 @@ import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import cosmoshub from "./testdata/cosmoshub.json"; import { deployedErc20, + fromOneElementArray, getRandomizedHackatom, makeRandomAddress, pendingWithoutWasmd, tendermintIdMatcher, wasmdEnabled, } from "./testutils.spec"; -import { CosmosSdkTx, MsgSend, StdFee } from "./types"; +import { CosmosSdkTx, isMsgSend, MsgSend, StdFee } from "./types"; const { fromAscii, fromHex, fromUtf8, toAscii, toBase64 } = Encoding; @@ -290,9 +291,20 @@ describe("CosmWasmClient", () => { pendingWithoutWasmd(); assert(posted, "value must be set in beforeAll()"); const client = new CosmWasmClient(httpUrl); - const result = await client.searchTx({ sentFromOrTo: posted.sender }); - expect(result.length).toBeGreaterThanOrEqual(1); - expect(result[result.length - 1]).toEqual( + const results = await client.searchTx({ sentFromOrTo: posted.sender }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); + expect(msg.value.to_address === posted.sender || msg.value.from_address == posted.sender).toEqual( + true, + ); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( jasmine.objectContaining({ height: posted.height.toString(), txhash: posted.hash, @@ -305,9 +317,20 @@ describe("CosmWasmClient", () => { pendingWithoutWasmd(); assert(posted, "value must be set in beforeAll()"); const client = new CosmWasmClient(httpUrl); - const result = await client.searchTx({ sentFromOrTo: posted.recipient }); - expect(result.length).toBeGreaterThanOrEqual(1); - expect(result[result.length - 1]).toEqual( + const results = await client.searchTx({ sentFromOrTo: posted.recipient }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); + expect( + msg.value.to_address === posted.recipient || msg.value.from_address == posted.recipient, + ).toEqual(true); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( jasmine.objectContaining({ height: posted.height.toString(), txhash: posted.hash, From df6db45e36a8c40dafc2d5edb23a32bbdcfd01ac Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 09:43:08 +0100 Subject: [PATCH 05/13] Add filter message.module=bank just in case --- packages/sdk/src/cosmwasmclient.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index 0ea8ef77..4bb27361 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -159,8 +159,10 @@ export class CosmWasmClient { } } else if (isSearchBySentFromOrToQuery(query)) { // We cannot get both in one request (see https://github.com/cosmos/gaia/issues/75) - const sent = await this.txsQuery(withFilters(`message.sender=${query.sentFromOrTo}`)); - const received = await this.txsQuery(withFilters(`transfer.recipient=${query.sentFromOrTo}`)); + const sentQuery = withFilters(`message.module=bank&message.sender=${query.sentFromOrTo}`); + const receivedQuery = withFilters(`transfer.recipient=${query.sentFromOrTo}`); + const sent = await this.txsQuery(sentQuery); + const received = await this.txsQuery(receivedQuery); const sentHashes = sent.map(t => t.txhash); txs = [...sent, ...received.filter(t => !sentHashes.includes(t.txhash))]; From b821a969a0b5daf250ee2784137d003430d217dd Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 10:27:55 +0100 Subject: [PATCH 06/13] Add transactionHash to ExecuteResult --- packages/sdk/src/cosmwasmclient.ts | 2 +- packages/sdk/src/signingcosmwasmclient.ts | 3 +++ packages/sdk/types/cosmwasmclient.d.ts | 2 +- packages/sdk/types/signingcosmwasmclient.d.ts | 2 ++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index 4bb27361..c173f5d6 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -13,7 +13,7 @@ export interface GetNonceResult { export interface PostTxResult { readonly logs: readonly Log[]; readonly rawLog: string; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-exmpty upper-case hex */ + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ readonly transactionHash: string; } diff --git a/packages/sdk/src/signingcosmwasmclient.ts b/packages/sdk/src/signingcosmwasmclient.ts index aa89a894..8a2616a5 100644 --- a/packages/sdk/src/signingcosmwasmclient.ts +++ b/packages/sdk/src/signingcosmwasmclient.ts @@ -83,6 +83,8 @@ export interface UploadReceipt { export interface ExecuteResult { readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; } export class SigningCosmWasmClient extends CosmWasmClient { @@ -220,6 +222,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { const result = await this.postTx(signedTx); return { logs: result.logs, + transactionHash: result.transactionHash, }; } diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index d001e6df..5b8ada44 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -8,7 +8,7 @@ export interface GetNonceResult { export interface PostTxResult { readonly logs: readonly Log[]; readonly rawLog: string; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-exmpty upper-case hex */ + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ readonly transactionHash: string; } export interface SearchByIdQuery { diff --git a/packages/sdk/types/signingcosmwasmclient.d.ts b/packages/sdk/types/signingcosmwasmclient.d.ts index 9510b2fa..318a387a 100644 --- a/packages/sdk/types/signingcosmwasmclient.d.ts +++ b/packages/sdk/types/signingcosmwasmclient.d.ts @@ -31,6 +31,8 @@ export interface UploadReceipt { } export interface ExecuteResult { readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; } export declare class SigningCosmWasmClient extends CosmWasmClient { readonly senderAddress: string; From 69e2e6b43ac51b600edfab2ff3c3bb49169d57ae Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 10:28:54 +0100 Subject: [PATCH 07/13] Implement and test CosmWasmClient.searchTx --- packages/sdk/src/cosmwasmclient.spec.ts | 283 +++++++++++++++++------- packages/sdk/src/cosmwasmclient.ts | 21 +- packages/sdk/src/testutils.spec.ts | 5 + packages/sdk/types/cosmwasmclient.d.ts | 16 +- 4 files changed, 249 insertions(+), 76 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 96b7cd44..b5c26c60 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -20,7 +20,15 @@ import { tendermintIdMatcher, wasmdEnabled, } from "./testutils.spec"; -import { CosmosSdkTx, isMsgSend, MsgSend, StdFee } from "./types"; +import { + Coin, + CosmosSdkTx, + isMsgExecuteContract, + isMsgInstantiateContract, + isMsgSend, + MsgSend, + StdFee, +} from "./types"; const { fromAscii, fromHex, fromUtf8, toAscii, toBase64 } = Encoding; @@ -213,7 +221,7 @@ describe("CosmWasmClient", () => { }); describe("searchTx", () => { - let posted: + let postedSend: | { readonly sender: string; readonly recipient: string; @@ -222,44 +230,72 @@ describe("CosmWasmClient", () => { readonly tx: CosmosSdkTx; } | undefined; + let postedExecute: + | { + readonly sender: string; + readonly contract: string; + readonly hash: string; + readonly height: number; + readonly tx: CosmosSdkTx; + } + | undefined; beforeAll(async () => { if (wasmdEnabled()) { const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); - const recipient = makeRandomAddress(); - const transferAmount = [ - { + { + const recipient = makeRandomAddress(); + const transferAmount: Coin = { denom: "ucosm", amount: "1234567", - }, - ]; - const result = await client.sendTokens(recipient, transferAmount); + }; + const result = await client.sendTokens(recipient, [transferAmount]); + await sleep(50); // wait until tx is indexed + const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); + postedSend = { + sender: faucet.address, + recipient: recipient, + hash: result.transactionHash, + height: Number.parseInt(txDetails.height, 10), + tx: txDetails.tx, + }; + } - await sleep(50); // wait until tx is indexed - const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); - posted = { - sender: faucet.address, - recipient: recipient, - hash: result.transactionHash, - height: Number.parseInt(txDetails.height, 10), - tx: txDetails.tx, - }; + { + const hashInstance = deployedErc20.instances[0]; + const msg = { + approve: { + spender: makeRandomAddress(), + amount: "12", + }, + }; + const result = await client.execute(hashInstance, msg); + await sleep(50); // wait until tx is indexed + const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); + postedExecute = { + sender: faucet.address, + contract: hashInstance, + hash: result.transactionHash, + height: Number.parseInt(txDetails.height, 10), + tx: txDetails.tx, + }; + } } }); it("can search by ID", async () => { pendingWithoutWasmd(); - assert(posted, "value must be set in beforeAll()"); + assert(postedSend, "value must be set in beforeAll()"); const client = new CosmWasmClient(httpUrl); - const result = await client.searchTx({ id: posted.hash }); + const result = await client.searchTx({ id: postedSend.hash }); expect(result.length).toEqual(1); expect(result[0]).toEqual( jasmine.objectContaining({ - height: posted.height.toString(), - txhash: posted.hash, - tx: posted.tx, + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, }), ); }); @@ -274,50 +310,24 @@ describe("CosmWasmClient", () => { it("can search by height", async () => { pendingWithoutWasmd(); - assert(posted, "value must be set in beforeAll()"); + assert(postedSend, "value must be set in beforeAll()"); const client = new CosmWasmClient(httpUrl); - const result = await client.searchTx({ height: posted.height }); + const result = await client.searchTx({ height: postedSend.height }); expect(result.length).toEqual(1); expect(result[0]).toEqual( jasmine.objectContaining({ - height: posted.height.toString(), - txhash: posted.hash, - tx: posted.tx, + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, }), ); }); it("can search by sender", async () => { pendingWithoutWasmd(); - assert(posted, "value must be set in beforeAll()"); + assert(postedSend, "value must be set in beforeAll()"); const client = new CosmWasmClient(httpUrl); - const results = await client.searchTx({ sentFromOrTo: posted.sender }); - expect(results.length).toBeGreaterThanOrEqual(1); - - // Check basic structure of all results - for (const result of results) { - const msg = fromOneElementArray(result.tx.value.msg); - assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); - expect(msg.value.to_address === posted.sender || msg.value.from_address == posted.sender).toEqual( - true, - ); - } - - // Check details of most recent result - expect(results[results.length - 1]).toEqual( - jasmine.objectContaining({ - height: posted.height.toString(), - txhash: posted.hash, - tx: posted.tx, - }), - ); - }); - - it("can search by recipient", async () => { - pendingWithoutWasmd(); - assert(posted, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); - const results = await client.searchTx({ sentFromOrTo: posted.recipient }); + const results = await client.searchTx({ sentFromOrTo: postedSend.sender }); expect(results.length).toBeGreaterThanOrEqual(1); // Check basic structure of all results @@ -325,25 +335,51 @@ describe("CosmWasmClient", () => { const msg = fromOneElementArray(result.tx.value.msg); assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); expect( - msg.value.to_address === posted.recipient || msg.value.from_address == posted.recipient, + msg.value.to_address === postedSend.sender || msg.value.from_address == postedSend.sender, ).toEqual(true); } // Check details of most recent result expect(results[results.length - 1]).toEqual( jasmine.objectContaining({ - height: posted.height.toString(), - txhash: posted.hash, - tx: posted.tx, + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, + }), + ); + }); + + it("can search by recipient", async () => { + pendingWithoutWasmd(); + assert(postedSend, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const results = await client.searchTx({ sentFromOrTo: postedSend.recipient }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); + expect( + msg.value.to_address === postedSend.recipient || msg.value.from_address == postedSend.recipient, + ).toEqual(true); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, }), ); }); it("can search by ID and filter by minHeight", async () => { pendingWithoutWasmd(); - assert(posted); + assert(postedSend); const client = new CosmWasmClient(httpUrl); - const query = { id: posted.hash }; + const query = { id: postedSend.hash }; { const result = await client.searchTx(query, { minHeight: 0 }); @@ -351,26 +387,26 @@ describe("CosmWasmClient", () => { } { - const result = await client.searchTx(query, { minHeight: posted.height - 1 }); + const result = await client.searchTx(query, { minHeight: postedSend.height - 1 }); expect(result.length).toEqual(1); } { - const result = await client.searchTx(query, { minHeight: posted.height }); + const result = await client.searchTx(query, { minHeight: postedSend.height }); expect(result.length).toEqual(1); } { - const result = await client.searchTx(query, { minHeight: posted.height + 1 }); + const result = await client.searchTx(query, { minHeight: postedSend.height + 1 }); expect(result.length).toEqual(0); } }); it("can search by recipient and filter by minHeight", async () => { pendingWithoutWasmd(); - assert(posted); + assert(postedSend); const client = new CosmWasmClient(httpUrl); - const query = { sentFromOrTo: posted.recipient }; + const query = { sentFromOrTo: postedSend.recipient }; { const result = await client.searchTx(query, { minHeight: 0 }); @@ -378,26 +414,26 @@ describe("CosmWasmClient", () => { } { - const result = await client.searchTx(query, { minHeight: posted.height - 1 }); + const result = await client.searchTx(query, { minHeight: postedSend.height - 1 }); expect(result.length).toEqual(1); } { - const result = await client.searchTx(query, { minHeight: posted.height }); + const result = await client.searchTx(query, { minHeight: postedSend.height }); expect(result.length).toEqual(1); } { - const result = await client.searchTx(query, { minHeight: posted.height + 1 }); + const result = await client.searchTx(query, { minHeight: postedSend.height + 1 }); expect(result.length).toEqual(0); } }); it("can search by recipient and filter by maxHeight", async () => { pendingWithoutWasmd(); - assert(posted); + assert(postedSend); const client = new CosmWasmClient(httpUrl); - const query = { sentFromOrTo: posted.recipient }; + const query = { sentFromOrTo: postedSend.recipient }; { const result = await client.searchTx(query, { maxHeight: 9999999999999 }); @@ -405,20 +441,119 @@ describe("CosmWasmClient", () => { } { - const result = await client.searchTx(query, { maxHeight: posted.height + 1 }); + const result = await client.searchTx(query, { maxHeight: postedSend.height + 1 }); expect(result.length).toEqual(1); } { - const result = await client.searchTx(query, { maxHeight: posted.height }); + const result = await client.searchTx(query, { maxHeight: postedSend.height }); expect(result.length).toEqual(1); } { - const result = await client.searchTx(query, { maxHeight: posted.height - 1 }); + const result = await client.searchTx(query, { maxHeight: postedSend.height - 1 }); expect(result.length).toEqual(0); } }); + + describe("with SearchByTagsQuery", () => { + it("can search by transfer.recipient", async () => { + pendingWithoutWasmd(); + assert(postedSend, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const results = await client.searchTx({ + tags: [{ key: "transfer.recipient", value: postedSend.recipient }], + }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); + expect(msg.value.to_address).toEqual(postedSend.recipient); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, + }), + ); + }); + + it("can search by message.contract_address", async () => { + pendingWithoutWasmd(); + assert(postedExecute, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const results = await client.searchTx({ + tags: [{ key: "message.contract_address", value: postedExecute.contract }], + }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert( + isMsgExecuteContract(msg) || isMsgInstantiateContract(msg), + `${result.txhash} (at ${result.height}) not an execute or instantiate msg`, + ); + } + + // Check that the first result is the instantiation + const first = fromOneElementArray(results[0].tx.value.msg); + assert(isMsgInstantiateContract(first), "First contract search result must be an instantiation"); + expect(first).toEqual({ + type: "wasm/instantiate", + value: { + sender: faucet.address, + code_id: deployedErc20.codeId.toString(), + label: "HASH", + init_msg: jasmine.objectContaining({ symbol: "HASH" }), + init_funds: [], + }, + }); + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedExecute.height.toString(), + txhash: postedExecute.hash, + tx: postedExecute.tx, + }), + ); + }); + + it("can search by message.contract_address + message.action", async () => { + pendingWithoutWasmd(); + assert(postedExecute, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const results = await client.searchTx({ + tags: [ + { key: "message.contract_address", value: postedExecute.contract }, + { key: "message.action", value: "execute" }, + ], + }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgExecuteContract(msg), `${result.txhash} (at ${result.height}) not an execute msg`); + expect(msg.value.contract).toEqual(postedExecute.contract); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedExecute.height.toString(), + txhash: postedExecute.hash, + tx: postedExecute.tx, + }), + ); + }); + }); }); describe("getCodes", () => { diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index c173f5d6..5ce22c5f 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -29,7 +29,19 @@ export interface SearchBySentFromOrToQuery { readonly sentFromOrTo: string; } -export type SearchTxQuery = SearchByIdQuery | SearchByHeightQuery | SearchBySentFromOrToQuery; +/** + * This query type allows you to pass arbitrary key/value pairs to the backend. It is + * more powerful and slightly lower level than the other search options. + */ +export interface SearchByTagsQuery { + readonly tags: readonly { readonly key: string; readonly value: string }[]; +} + +export type SearchTxQuery = + | SearchByIdQuery + | SearchByHeightQuery + | SearchBySentFromOrToQuery + | SearchByTagsQuery; function isSearchByIdQuery(query: SearchTxQuery): query is SearchByIdQuery { return (query as SearchByIdQuery).id !== undefined; @@ -43,6 +55,10 @@ function isSearchBySentFromOrToQuery(query: SearchTxQuery): query is SearchBySen return (query as SearchBySentFromOrToQuery).sentFromOrTo !== undefined; } +function isSearchByTagsQuery(query: SearchTxQuery): query is SearchByTagsQuery { + return (query as SearchByTagsQuery).tags !== undefined; +} + export interface SearchTxFilter { readonly minHeight?: number; readonly maxHeight?: number; @@ -166,6 +182,9 @@ export class CosmWasmClient { const sentHashes = sent.map(t => t.txhash); txs = [...sent, ...received.filter(t => !sentHashes.includes(t.txhash))]; + } else if (isSearchByTagsQuery(query)) { + const rawQuery = withFilters(query.tags.map(t => `${t.key}=${t.value}`).join("&")); + txs = await this.txsQuery(rawQuery); } else { throw new Error("Unknown query type"); } diff --git a/packages/sdk/src/testutils.spec.ts b/packages/sdk/src/testutils.spec.ts index d072842f..78f3c090 100644 --- a/packages/sdk/src/testutils.spec.ts +++ b/packages/sdk/src/testutils.spec.ts @@ -69,6 +69,11 @@ export const deployedErc20 = { source: "https://crates.io/api/v1/crates/cw-erc20/0.2.0/download", builder: "confio/cosmwasm-opt:0.7.0", checksum: "aff8c8873d79d2153a8b9066a0683fec3c903669267eb806ffa831dcd4b3daae", + instances: [ + "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", // HASH + "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd", // ISA + "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c", // JADE + ], }; export function wasmdEnabled(): boolean { diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index 5b8ada44..f544e060 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -20,7 +20,21 @@ export interface SearchByHeightQuery { export interface SearchBySentFromOrToQuery { readonly sentFromOrTo: string; } -export declare type SearchTxQuery = SearchByIdQuery | SearchByHeightQuery | SearchBySentFromOrToQuery; +/** + * This query type allows you to pass arbitrary key/value pairs to the backend. It is + * more powerful and slightly lower level than the other search options. + */ +export interface SearchByTagsQuery { + readonly tags: readonly { + readonly key: string; + readonly value: string; + }[]; +} +export declare type SearchTxQuery = + | SearchByIdQuery + | SearchByHeightQuery + | SearchBySentFromOrToQuery + | SearchByTagsQuery; export interface SearchTxFilter { readonly minHeight?: number; readonly maxHeight?: number; From ada7b7d2c4a96f57ba1822406bc0fd5723b14583 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 10:32:38 +0100 Subject: [PATCH 08/13] Extract CosmWasmClient.searchTx --- .../sdk/src/cosmwasmclient.searchtx.spec.ts | 363 ++++++++++++++++++ packages/sdk/src/cosmwasmclient.spec.ts | 350 +---------------- 2 files changed, 365 insertions(+), 348 deletions(-) create mode 100644 packages/sdk/src/cosmwasmclient.searchtx.spec.ts diff --git a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts new file mode 100644 index 00000000..0e5eac70 --- /dev/null +++ b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts @@ -0,0 +1,363 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { assert, sleep } from "@iov/utils"; + +import { CosmWasmClient } from "./cosmwasmclient"; +import { Secp256k1Pen } from "./pen"; +import { RestClient } from "./restclient"; +import { SigningCosmWasmClient } from "./signingcosmwasmclient"; +import { + deployedErc20, + fromOneElementArray, + makeRandomAddress, + pendingWithoutWasmd, + wasmdEnabled, +} from "./testutils.spec"; +import { Coin, CosmosSdkTx, isMsgExecuteContract, isMsgInstantiateContract, isMsgSend } from "./types"; + +const httpUrl = "http://localhost:1317"; + +const faucet = { + mnemonic: + "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone", + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }, + address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", +}; + +fdescribe("CosmWasmClient.searchTx", () => { + let postedSend: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + readonly height: number; + readonly tx: CosmosSdkTx; + } + | undefined; + let postedExecute: + | { + readonly sender: string; + readonly contract: string; + readonly hash: string; + readonly height: number; + readonly tx: CosmosSdkTx; + } + | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); + + { + const recipient = makeRandomAddress(); + const transferAmount: Coin = { + denom: "ucosm", + amount: "1234567", + }; + const result = await client.sendTokens(recipient, [transferAmount]); + await sleep(50); // wait until tx is indexed + const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); + postedSend = { + sender: faucet.address, + recipient: recipient, + hash: result.transactionHash, + height: Number.parseInt(txDetails.height, 10), + tx: txDetails.tx, + }; + } + + { + const hashInstance = deployedErc20.instances[0]; + const msg = { + approve: { + spender: makeRandomAddress(), + amount: "12", + }, + }; + const result = await client.execute(hashInstance, msg); + await sleep(50); // wait until tx is indexed + const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); + postedExecute = { + sender: faucet.address, + contract: hashInstance, + hash: result.transactionHash, + height: Number.parseInt(txDetails.height, 10), + tx: txDetails.tx, + }; + } + } + }); + + it("can search by ID", async () => { + pendingWithoutWasmd(); + assert(postedSend, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const result = await client.searchTx({ id: postedSend.hash }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, + }), + ); + }); + + it("can search by ID (non existent)", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(httpUrl); + const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000"; + const result = await client.searchTx({ id: nonExistentId }); + expect(result.length).toEqual(0); + }); + + it("can search by height", async () => { + pendingWithoutWasmd(); + assert(postedSend, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const result = await client.searchTx({ height: postedSend.height }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, + }), + ); + }); + + it("can search by sender", async () => { + pendingWithoutWasmd(); + assert(postedSend, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const results = await client.searchTx({ sentFromOrTo: postedSend.sender }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); + expect( + msg.value.to_address === postedSend.sender || msg.value.from_address == postedSend.sender, + ).toEqual(true); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, + }), + ); + }); + + it("can search by recipient", async () => { + pendingWithoutWasmd(); + assert(postedSend, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const results = await client.searchTx({ sentFromOrTo: postedSend.recipient }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); + expect( + msg.value.to_address === postedSend.recipient || msg.value.from_address == postedSend.recipient, + ).toEqual(true); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, + }), + ); + }); + + it("can search by ID and filter by minHeight", async () => { + pendingWithoutWasmd(); + assert(postedSend); + const client = new CosmWasmClient(httpUrl); + const query = { id: postedSend.hash }; + + { + const result = await client.searchTx(query, { minHeight: 0 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: postedSend.height - 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: postedSend.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: postedSend.height + 1 }); + expect(result.length).toEqual(0); + } + }); + + it("can search by recipient and filter by minHeight", async () => { + pendingWithoutWasmd(); + assert(postedSend); + const client = new CosmWasmClient(httpUrl); + const query = { sentFromOrTo: postedSend.recipient }; + + { + const result = await client.searchTx(query, { minHeight: 0 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: postedSend.height - 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: postedSend.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: postedSend.height + 1 }); + expect(result.length).toEqual(0); + } + }); + + it("can search by recipient and filter by maxHeight", async () => { + pendingWithoutWasmd(); + assert(postedSend); + const client = new CosmWasmClient(httpUrl); + const query = { sentFromOrTo: postedSend.recipient }; + + { + const result = await client.searchTx(query, { maxHeight: 9999999999999 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: postedSend.height + 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: postedSend.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: postedSend.height - 1 }); + expect(result.length).toEqual(0); + } + }); + + describe("with SearchByTagsQuery", () => { + it("can search by transfer.recipient", async () => { + pendingWithoutWasmd(); + assert(postedSend, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const results = await client.searchTx({ + tags: [{ key: "transfer.recipient", value: postedSend.recipient }], + }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); + expect(msg.value.to_address).toEqual(postedSend.recipient); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, + }), + ); + }); + + it("can search by message.contract_address", async () => { + pendingWithoutWasmd(); + assert(postedExecute, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const results = await client.searchTx({ + tags: [{ key: "message.contract_address", value: postedExecute.contract }], + }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert( + isMsgExecuteContract(msg) || isMsgInstantiateContract(msg), + `${result.txhash} (at ${result.height}) not an execute or instantiate msg`, + ); + } + + // Check that the first result is the instantiation + const first = fromOneElementArray(results[0].tx.value.msg); + assert(isMsgInstantiateContract(first), "First contract search result must be an instantiation"); + expect(first).toEqual({ + type: "wasm/instantiate", + value: { + sender: faucet.address, + code_id: deployedErc20.codeId.toString(), + label: "HASH", + init_msg: jasmine.objectContaining({ symbol: "HASH" }), + init_funds: [], + }, + }); + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedExecute.height.toString(), + txhash: postedExecute.hash, + tx: postedExecute.tx, + }), + ); + }); + + it("can search by message.contract_address + message.action", async () => { + pendingWithoutWasmd(); + assert(postedExecute, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const results = await client.searchTx({ + tags: [ + { key: "message.contract_address", value: postedExecute.contract }, + { key: "message.action", value: "execute" }, + ], + }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgExecuteContract(msg), `${result.txhash} (at ${result.height}) not an execute msg`); + expect(msg.value.contract).toEqual(postedExecute.contract); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedExecute.height.toString(), + txhash: postedExecute.hash, + tx: postedExecute.tx, + }), + ); + }); + }); +}); diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index b5c26c60..4a395e3c 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -1,34 +1,24 @@ /* eslint-disable @typescript-eslint/camelcase */ import { Sha256 } from "@iov/crypto"; import { Bech32, Encoding } from "@iov/encoding"; -import { assert, sleep } from "@iov/utils"; +import { assert } from "@iov/utils"; import { ReadonlyDate } from "readonly-date"; import { Code, CosmWasmClient } from "./cosmwasmclient"; import { makeSignBytes } from "./encoding"; import { findAttribute } from "./logs"; import { Secp256k1Pen } from "./pen"; -import { RestClient } from "./restclient"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import cosmoshub from "./testdata/cosmoshub.json"; import { deployedErc20, - fromOneElementArray, getRandomizedHackatom, makeRandomAddress, pendingWithoutWasmd, tendermintIdMatcher, wasmdEnabled, } from "./testutils.spec"; -import { - Coin, - CosmosSdkTx, - isMsgExecuteContract, - isMsgInstantiateContract, - isMsgSend, - MsgSend, - StdFee, -} from "./types"; +import { MsgSend, StdFee } from "./types"; const { fromAscii, fromHex, fromUtf8, toAscii, toBase64 } = Encoding; @@ -220,342 +210,6 @@ describe("CosmWasmClient", () => { }); }); - describe("searchTx", () => { - let postedSend: - | { - readonly sender: string; - readonly recipient: string; - readonly hash: string; - readonly height: number; - readonly tx: CosmosSdkTx; - } - | undefined; - let postedExecute: - | { - readonly sender: string; - readonly contract: string; - readonly hash: string; - readonly height: number; - readonly tx: CosmosSdkTx; - } - | undefined; - - beforeAll(async () => { - if (wasmdEnabled()) { - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); - - { - const recipient = makeRandomAddress(); - const transferAmount: Coin = { - denom: "ucosm", - amount: "1234567", - }; - const result = await client.sendTokens(recipient, [transferAmount]); - await sleep(50); // wait until tx is indexed - const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); - postedSend = { - sender: faucet.address, - recipient: recipient, - hash: result.transactionHash, - height: Number.parseInt(txDetails.height, 10), - tx: txDetails.tx, - }; - } - - { - const hashInstance = deployedErc20.instances[0]; - const msg = { - approve: { - spender: makeRandomAddress(), - amount: "12", - }, - }; - const result = await client.execute(hashInstance, msg); - await sleep(50); // wait until tx is indexed - const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); - postedExecute = { - sender: faucet.address, - contract: hashInstance, - hash: result.transactionHash, - height: Number.parseInt(txDetails.height, 10), - tx: txDetails.tx, - }; - } - } - }); - - it("can search by ID", async () => { - pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); - const result = await client.searchTx({ id: postedSend.hash }); - expect(result.length).toEqual(1); - expect(result[0]).toEqual( - jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, - tx: postedSend.tx, - }), - ); - }); - - it("can search by ID (non existent)", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); - const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000"; - const result = await client.searchTx({ id: nonExistentId }); - expect(result.length).toEqual(0); - }); - - it("can search by height", async () => { - pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); - const result = await client.searchTx({ height: postedSend.height }); - expect(result.length).toEqual(1); - expect(result[0]).toEqual( - jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, - tx: postedSend.tx, - }), - ); - }); - - it("can search by sender", async () => { - pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); - const results = await client.searchTx({ sentFromOrTo: postedSend.sender }); - expect(results.length).toBeGreaterThanOrEqual(1); - - // Check basic structure of all results - for (const result of results) { - const msg = fromOneElementArray(result.tx.value.msg); - assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); - expect( - msg.value.to_address === postedSend.sender || msg.value.from_address == postedSend.sender, - ).toEqual(true); - } - - // Check details of most recent result - expect(results[results.length - 1]).toEqual( - jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, - tx: postedSend.tx, - }), - ); - }); - - it("can search by recipient", async () => { - pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); - const results = await client.searchTx({ sentFromOrTo: postedSend.recipient }); - expect(results.length).toBeGreaterThanOrEqual(1); - - // Check basic structure of all results - for (const result of results) { - const msg = fromOneElementArray(result.tx.value.msg); - assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); - expect( - msg.value.to_address === postedSend.recipient || msg.value.from_address == postedSend.recipient, - ).toEqual(true); - } - - // Check details of most recent result - expect(results[results.length - 1]).toEqual( - jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, - tx: postedSend.tx, - }), - ); - }); - - it("can search by ID and filter by minHeight", async () => { - pendingWithoutWasmd(); - assert(postedSend); - const client = new CosmWasmClient(httpUrl); - const query = { id: postedSend.hash }; - - { - const result = await client.searchTx(query, { minHeight: 0 }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { minHeight: postedSend.height - 1 }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { minHeight: postedSend.height }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { minHeight: postedSend.height + 1 }); - expect(result.length).toEqual(0); - } - }); - - it("can search by recipient and filter by minHeight", async () => { - pendingWithoutWasmd(); - assert(postedSend); - const client = new CosmWasmClient(httpUrl); - const query = { sentFromOrTo: postedSend.recipient }; - - { - const result = await client.searchTx(query, { minHeight: 0 }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { minHeight: postedSend.height - 1 }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { minHeight: postedSend.height }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { minHeight: postedSend.height + 1 }); - expect(result.length).toEqual(0); - } - }); - - it("can search by recipient and filter by maxHeight", async () => { - pendingWithoutWasmd(); - assert(postedSend); - const client = new CosmWasmClient(httpUrl); - const query = { sentFromOrTo: postedSend.recipient }; - - { - const result = await client.searchTx(query, { maxHeight: 9999999999999 }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { maxHeight: postedSend.height + 1 }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { maxHeight: postedSend.height }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { maxHeight: postedSend.height - 1 }); - expect(result.length).toEqual(0); - } - }); - - describe("with SearchByTagsQuery", () => { - it("can search by transfer.recipient", async () => { - pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); - const results = await client.searchTx({ - tags: [{ key: "transfer.recipient", value: postedSend.recipient }], - }); - expect(results.length).toBeGreaterThanOrEqual(1); - - // Check basic structure of all results - for (const result of results) { - const msg = fromOneElementArray(result.tx.value.msg); - assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); - expect(msg.value.to_address).toEqual(postedSend.recipient); - } - - // Check details of most recent result - expect(results[results.length - 1]).toEqual( - jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, - tx: postedSend.tx, - }), - ); - }); - - it("can search by message.contract_address", async () => { - pendingWithoutWasmd(); - assert(postedExecute, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); - const results = await client.searchTx({ - tags: [{ key: "message.contract_address", value: postedExecute.contract }], - }); - expect(results.length).toBeGreaterThanOrEqual(1); - - // Check basic structure of all results - for (const result of results) { - const msg = fromOneElementArray(result.tx.value.msg); - assert( - isMsgExecuteContract(msg) || isMsgInstantiateContract(msg), - `${result.txhash} (at ${result.height}) not an execute or instantiate msg`, - ); - } - - // Check that the first result is the instantiation - const first = fromOneElementArray(results[0].tx.value.msg); - assert(isMsgInstantiateContract(first), "First contract search result must be an instantiation"); - expect(first).toEqual({ - type: "wasm/instantiate", - value: { - sender: faucet.address, - code_id: deployedErc20.codeId.toString(), - label: "HASH", - init_msg: jasmine.objectContaining({ symbol: "HASH" }), - init_funds: [], - }, - }); - - // Check details of most recent result - expect(results[results.length - 1]).toEqual( - jasmine.objectContaining({ - height: postedExecute.height.toString(), - txhash: postedExecute.hash, - tx: postedExecute.tx, - }), - ); - }); - - it("can search by message.contract_address + message.action", async () => { - pendingWithoutWasmd(); - assert(postedExecute, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); - const results = await client.searchTx({ - tags: [ - { key: "message.contract_address", value: postedExecute.contract }, - { key: "message.action", value: "execute" }, - ], - }); - expect(results.length).toBeGreaterThanOrEqual(1); - - // Check basic structure of all results - for (const result of results) { - const msg = fromOneElementArray(result.tx.value.msg); - assert(isMsgExecuteContract(msg), `${result.txhash} (at ${result.height}) not an execute msg`); - expect(msg.value.contract).toEqual(postedExecute.contract); - } - - // Check details of most recent result - expect(results[results.length - 1]).toEqual( - jasmine.objectContaining({ - height: postedExecute.height.toString(), - txhash: postedExecute.hash, - tx: postedExecute.tx, - }), - ); - }); - }); - }); - describe("getCodes", () => { it("works", async () => { pendingWithoutWasmd(); From a43a81b0ff3a9018186bd23dd383cf727ac8a1a5 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 10:36:32 +0100 Subject: [PATCH 09/13] Group CosmWasmClient.searchTx tests by query type --- .../sdk/src/cosmwasmclient.searchtx.spec.ts | 322 +++++++++--------- 1 file changed, 164 insertions(+), 158 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts index 0e5eac70..5fb0f060 100644 --- a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts +++ b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts @@ -26,7 +26,7 @@ const faucet = { address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", }; -fdescribe("CosmWasmClient.searchTx", () => { +describe("CosmWasmClient.searchTx", () => { let postedSend: | { readonly sender: string; @@ -91,175 +91,181 @@ fdescribe("CosmWasmClient.searchTx", () => { } }); - it("can search by ID", async () => { - pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); - const result = await client.searchTx({ id: postedSend.hash }); - expect(result.length).toEqual(1); - expect(result[0]).toEqual( - jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, - tx: postedSend.tx, - }), - ); - }); - - it("can search by ID (non existent)", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); - const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000"; - const result = await client.searchTx({ id: nonExistentId }); - expect(result.length).toEqual(0); - }); - - it("can search by height", async () => { - pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); - const result = await client.searchTx({ height: postedSend.height }); - expect(result.length).toEqual(1); - expect(result[0]).toEqual( - jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, - tx: postedSend.tx, - }), - ); - }); - - it("can search by sender", async () => { - pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); - const results = await client.searchTx({ sentFromOrTo: postedSend.sender }); - expect(results.length).toBeGreaterThanOrEqual(1); - - // Check basic structure of all results - for (const result of results) { - const msg = fromOneElementArray(result.tx.value.msg); - assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); - expect( - msg.value.to_address === postedSend.sender || msg.value.from_address == postedSend.sender, - ).toEqual(true); - } - - // Check details of most recent result - expect(results[results.length - 1]).toEqual( - jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, - tx: postedSend.tx, - }), - ); - }); - - it("can search by recipient", async () => { - pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); - const results = await client.searchTx({ sentFromOrTo: postedSend.recipient }); - expect(results.length).toBeGreaterThanOrEqual(1); - - // Check basic structure of all results - for (const result of results) { - const msg = fromOneElementArray(result.tx.value.msg); - assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); - expect( - msg.value.to_address === postedSend.recipient || msg.value.from_address == postedSend.recipient, - ).toEqual(true); - } - - // Check details of most recent result - expect(results[results.length - 1]).toEqual( - jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, - tx: postedSend.tx, - }), - ); - }); - - it("can search by ID and filter by minHeight", async () => { - pendingWithoutWasmd(); - assert(postedSend); - const client = new CosmWasmClient(httpUrl); - const query = { id: postedSend.hash }; - - { - const result = await client.searchTx(query, { minHeight: 0 }); + describe("with SearchByIdQuery", () => { + it("can search by ID", async () => { + pendingWithoutWasmd(); + assert(postedSend, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const result = await client.searchTx({ id: postedSend.hash }); expect(result.length).toEqual(1); - } + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, + }), + ); + }); - { - const result = await client.searchTx(query, { minHeight: postedSend.height - 1 }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { minHeight: postedSend.height }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { minHeight: postedSend.height + 1 }); + it("can search by ID (non existent)", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(httpUrl); + const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000"; + const result = await client.searchTx({ id: nonExistentId }); expect(result.length).toEqual(0); - } + }); + + it("can search by ID and filter by minHeight", async () => { + pendingWithoutWasmd(); + assert(postedSend); + const client = new CosmWasmClient(httpUrl); + const query = { id: postedSend.hash }; + + { + const result = await client.searchTx(query, { minHeight: 0 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: postedSend.height - 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: postedSend.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: postedSend.height + 1 }); + expect(result.length).toEqual(0); + } + }); }); - it("can search by recipient and filter by minHeight", async () => { - pendingWithoutWasmd(); - assert(postedSend); - const client = new CosmWasmClient(httpUrl); - const query = { sentFromOrTo: postedSend.recipient }; - - { - const result = await client.searchTx(query, { minHeight: 0 }); + describe("with SearchByHeightQuery", () => { + it("can search by height", async () => { + pendingWithoutWasmd(); + assert(postedSend, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const result = await client.searchTx({ height: postedSend.height }); expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { minHeight: postedSend.height - 1 }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { minHeight: postedSend.height }); - expect(result.length).toEqual(1); - } - - { - const result = await client.searchTx(query, { minHeight: postedSend.height + 1 }); - expect(result.length).toEqual(0); - } + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, + }), + ); + }); }); - it("can search by recipient and filter by maxHeight", async () => { - pendingWithoutWasmd(); - assert(postedSend); - const client = new CosmWasmClient(httpUrl); - const query = { sentFromOrTo: postedSend.recipient }; + describe("with SearchBySentFromOrToQuery", () => { + it("can search by sender", async () => { + pendingWithoutWasmd(); + assert(postedSend, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const results = await client.searchTx({ sentFromOrTo: postedSend.sender }); + expect(results.length).toBeGreaterThanOrEqual(1); - { - const result = await client.searchTx(query, { maxHeight: 9999999999999 }); - expect(result.length).toEqual(1); - } + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); + expect( + msg.value.to_address === postedSend.sender || msg.value.from_address == postedSend.sender, + ).toEqual(true); + } - { - const result = await client.searchTx(query, { maxHeight: postedSend.height + 1 }); - expect(result.length).toEqual(1); - } + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, + }), + ); + }); - { - const result = await client.searchTx(query, { maxHeight: postedSend.height }); - expect(result.length).toEqual(1); - } + it("can search by recipient", async () => { + pendingWithoutWasmd(); + assert(postedSend, "value must be set in beforeAll()"); + const client = new CosmWasmClient(httpUrl); + const results = await client.searchTx({ sentFromOrTo: postedSend.recipient }); + expect(results.length).toBeGreaterThanOrEqual(1); - { - const result = await client.searchTx(query, { maxHeight: postedSend.height - 1 }); - expect(result.length).toEqual(0); - } + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`); + expect( + msg.value.to_address === postedSend.recipient || msg.value.from_address == postedSend.recipient, + ).toEqual(true); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedSend.height.toString(), + txhash: postedSend.hash, + tx: postedSend.tx, + }), + ); + }); + + it("can search by recipient and filter by minHeight", async () => { + pendingWithoutWasmd(); + assert(postedSend); + const client = new CosmWasmClient(httpUrl); + const query = { sentFromOrTo: postedSend.recipient }; + + { + const result = await client.searchTx(query, { minHeight: 0 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: postedSend.height - 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: postedSend.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: postedSend.height + 1 }); + expect(result.length).toEqual(0); + } + }); + + it("can search by recipient and filter by maxHeight", async () => { + pendingWithoutWasmd(); + assert(postedSend); + const client = new CosmWasmClient(httpUrl); + const query = { sentFromOrTo: postedSend.recipient }; + + { + const result = await client.searchTx(query, { maxHeight: 9999999999999 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: postedSend.height + 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: postedSend.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: postedSend.height - 1 }); + expect(result.length).toEqual(0); + } + }); }); describe("with SearchByTagsQuery", () => { From 5aca5ebd695fa206f488187ec358b66ae913172c Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 10:40:38 +0100 Subject: [PATCH 10/13] Dedup wasmdEndpoint, faucet --- .../sdk/src/cosmwasmclient.searchtx.spec.ts | 44 ++++++------- packages/sdk/src/cosmwasmclient.spec.ts | 62 +++++++++---------- packages/sdk/src/restclient.spec.ts | 62 +++++++++---------- packages/sdk/src/testutils.spec.ts | 12 ++++ 4 files changed, 86 insertions(+), 94 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts index 5fb0f060..5079a6bd 100644 --- a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts +++ b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts @@ -7,25 +7,15 @@ import { RestClient } from "./restclient"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import { deployedErc20, + faucet, fromOneElementArray, makeRandomAddress, pendingWithoutWasmd, wasmdEnabled, + wasmdEndpoint, } from "./testutils.spec"; import { Coin, CosmosSdkTx, isMsgExecuteContract, isMsgInstantiateContract, isMsgSend } from "./types"; -const httpUrl = "http://localhost:1317"; - -const faucet = { - mnemonic: - "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone", - pubkey: { - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }, - address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", -}; - describe("CosmWasmClient.searchTx", () => { let postedSend: | { @@ -49,7 +39,9 @@ describe("CosmWasmClient.searchTx", () => { beforeAll(async () => { if (wasmdEnabled()) { const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); + const client = new SigningCosmWasmClient(wasmdEndpoint, faucet.address, signBytes => + pen.sign(signBytes), + ); { const recipient = makeRandomAddress(); @@ -59,7 +51,7 @@ describe("CosmWasmClient.searchTx", () => { }; const result = await client.sendTokens(recipient, [transferAmount]); await sleep(50); // wait until tx is indexed - const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); + const txDetails = await new RestClient(wasmdEndpoint).txsById(result.transactionHash); postedSend = { sender: faucet.address, recipient: recipient, @@ -79,7 +71,7 @@ describe("CosmWasmClient.searchTx", () => { }; const result = await client.execute(hashInstance, msg); await sleep(50); // wait until tx is indexed - const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); + const txDetails = await new RestClient(wasmdEndpoint).txsById(result.transactionHash); postedExecute = { sender: faucet.address, contract: hashInstance, @@ -95,7 +87,7 @@ describe("CosmWasmClient.searchTx", () => { it("can search by ID", async () => { pendingWithoutWasmd(); assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const result = await client.searchTx({ id: postedSend.hash }); expect(result.length).toEqual(1); expect(result[0]).toEqual( @@ -109,7 +101,7 @@ describe("CosmWasmClient.searchTx", () => { it("can search by ID (non existent)", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000"; const result = await client.searchTx({ id: nonExistentId }); expect(result.length).toEqual(0); @@ -118,7 +110,7 @@ describe("CosmWasmClient.searchTx", () => { it("can search by ID and filter by minHeight", async () => { pendingWithoutWasmd(); assert(postedSend); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const query = { id: postedSend.hash }; { @@ -147,7 +139,7 @@ describe("CosmWasmClient.searchTx", () => { it("can search by height", async () => { pendingWithoutWasmd(); assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const result = await client.searchTx({ height: postedSend.height }); expect(result.length).toEqual(1); expect(result[0]).toEqual( @@ -164,7 +156,7 @@ describe("CosmWasmClient.searchTx", () => { it("can search by sender", async () => { pendingWithoutWasmd(); assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const results = await client.searchTx({ sentFromOrTo: postedSend.sender }); expect(results.length).toBeGreaterThanOrEqual(1); @@ -190,7 +182,7 @@ describe("CosmWasmClient.searchTx", () => { it("can search by recipient", async () => { pendingWithoutWasmd(); assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const results = await client.searchTx({ sentFromOrTo: postedSend.recipient }); expect(results.length).toBeGreaterThanOrEqual(1); @@ -216,7 +208,7 @@ describe("CosmWasmClient.searchTx", () => { it("can search by recipient and filter by minHeight", async () => { pendingWithoutWasmd(); assert(postedSend); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const query = { sentFromOrTo: postedSend.recipient }; { @@ -243,7 +235,7 @@ describe("CosmWasmClient.searchTx", () => { it("can search by recipient and filter by maxHeight", async () => { pendingWithoutWasmd(); assert(postedSend); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const query = { sentFromOrTo: postedSend.recipient }; { @@ -272,7 +264,7 @@ describe("CosmWasmClient.searchTx", () => { it("can search by transfer.recipient", async () => { pendingWithoutWasmd(); assert(postedSend, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const results = await client.searchTx({ tags: [{ key: "transfer.recipient", value: postedSend.recipient }], }); @@ -298,7 +290,7 @@ describe("CosmWasmClient.searchTx", () => { it("can search by message.contract_address", async () => { pendingWithoutWasmd(); assert(postedExecute, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const results = await client.searchTx({ tags: [{ key: "message.contract_address", value: postedExecute.contract }], }); @@ -340,7 +332,7 @@ describe("CosmWasmClient.searchTx", () => { it("can search by message.contract_address + message.action", async () => { pendingWithoutWasmd(); assert(postedExecute, "value must be set in beforeAll()"); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const results = await client.searchTx({ tags: [ { key: "message.contract_address", value: postedExecute.contract }, diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 4a395e3c..773816a3 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -12,28 +12,18 @@ import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import cosmoshub from "./testdata/cosmoshub.json"; import { deployedErc20, + faucet, getRandomizedHackatom, makeRandomAddress, pendingWithoutWasmd, tendermintIdMatcher, wasmdEnabled, + wasmdEndpoint, } from "./testutils.spec"; import { MsgSend, StdFee } from "./types"; const { fromAscii, fromHex, fromUtf8, toAscii, toBase64 } = Encoding; -const httpUrl = "http://localhost:1317"; - -const faucet = { - mnemonic: - "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone", - pubkey: { - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }, - address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", -}; - const unused = { address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u", }; @@ -53,7 +43,7 @@ interface HackatomInstance { describe("CosmWasmClient", () => { describe("makeReadOnly", () => { it("can be constructed", () => { - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); expect(client).toBeTruthy(); }); }); @@ -61,7 +51,7 @@ describe("CosmWasmClient", () => { describe("chainId", () => { it("works", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); expect(await client.chainId()).toEqual("testing"); }); }); @@ -69,7 +59,7 @@ describe("CosmWasmClient", () => { describe("getNonce", () => { it("works", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); expect(await client.getNonce(unused.address)).toEqual({ accountNumber: 5, sequence: 0, @@ -78,7 +68,7 @@ describe("CosmWasmClient", () => { it("throws for missing accounts", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const missing = makeRandomAddress(); await client.getNonce(missing).then( () => fail("this must not succeed"), @@ -90,7 +80,7 @@ describe("CosmWasmClient", () => { describe("getAccount", () => { it("works", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); expect(await client.getAccount(unused.address)).toEqual({ address: unused.address, account_number: 5, @@ -105,7 +95,7 @@ describe("CosmWasmClient", () => { it("returns undefined for missing accounts", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const missing = makeRandomAddress(); expect(await client.getAccount(missing)).toBeUndefined(); }); @@ -114,7 +104,7 @@ describe("CosmWasmClient", () => { describe("getBlock", () => { it("works for latest block", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const response = await client.getBlock(); // id @@ -134,7 +124,7 @@ describe("CosmWasmClient", () => { it("works for block by height", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const height = parseInt((await client.getBlock()).block.header.height, 10); const response = await client.getBlock(height - 1); @@ -157,7 +147,7 @@ describe("CosmWasmClient", () => { describe("getIdentifier", () => { it("works", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); expect(await client.getIdentifier(cosmoshub.tx)).toEqual(cosmoshub.id); }); }); @@ -166,7 +156,7 @@ describe("CosmWasmClient", () => { it("works", async () => { pendingWithoutWasmd(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const memo = "My first contract on chain"; const sendMsg: MsgSend = { @@ -213,7 +203,7 @@ describe("CosmWasmClient", () => { describe("getCodes", () => { it("works", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const result = await client.getCodes(); expect(result.length).toBeGreaterThanOrEqual(1); const [first] = result; @@ -230,7 +220,7 @@ describe("CosmWasmClient", () => { describe("getCodeDetails", () => { it("works", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const result = await client.getCodeDetails(1); const expectedInfo: Code = { @@ -251,7 +241,7 @@ describe("CosmWasmClient", () => { describe("getContracts", () => { it("works", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const result = await client.getContracts(1); expect(result.length).toBeGreaterThanOrEqual(3); const [hash, isa, jade] = result; @@ -279,7 +269,7 @@ describe("CosmWasmClient", () => { describe("getContract", () => { it("works for HASH instance", async () => { pendingWithoutWasmd(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const hash = await client.getContract("cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5"); expect(hash).toEqual({ address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", @@ -318,7 +308,9 @@ describe("CosmWasmClient", () => { if (wasmdEnabled()) { pendingWithoutWasmd(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); + const client = new SigningCosmWasmClient(wasmdEndpoint, 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, "random hackatom"); @@ -330,7 +322,7 @@ describe("CosmWasmClient", () => { pendingWithoutWasmd(); assert(contract); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const raw = await client.queryContractRaw(contract.address, configKey); assert(raw, "must get result"); expect(JSON.parse(fromUtf8(raw))).toEqual({ @@ -344,7 +336,7 @@ describe("CosmWasmClient", () => { pendingWithoutWasmd(); assert(contract); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const raw = await client.queryContractRaw(contract.address, otherKey); expect(raw).toBeNull(); }); @@ -354,7 +346,7 @@ describe("CosmWasmClient", () => { assert(contract); const nonExistentAddress = makeRandomAddress(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); await client.queryContractRaw(nonExistentAddress, configKey).then( () => fail("must not succeed"), error => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`), @@ -369,7 +361,9 @@ describe("CosmWasmClient", () => { if (wasmdEnabled()) { pendingWithoutWasmd(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); + const client = new SigningCosmWasmClient(wasmdEndpoint, 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, "a different hackatom"); @@ -381,7 +375,7 @@ describe("CosmWasmClient", () => { pendingWithoutWasmd(); assert(contract); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); const verifier = await client.queryContractSmart(contract.address, { verifier: {} }); expect(fromAscii(verifier)).toEqual(contract.initMsg.verifier); }); @@ -390,7 +384,7 @@ describe("CosmWasmClient", () => { pendingWithoutWasmd(); assert(contract); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); await client.queryContractSmart(contract.address, { broken: {} }).then( () => fail("must not succeed"), error => expect(error).toMatch(/Error parsing QueryMsg/i), @@ -401,7 +395,7 @@ describe("CosmWasmClient", () => { pendingWithoutWasmd(); const nonExistentAddress = makeRandomAddress(); - const client = new CosmWasmClient(httpUrl); + const client = new CosmWasmClient(wasmdEndpoint); 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/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 03b95e8e..28e95ce1 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -14,6 +14,7 @@ import cosmoshub from "./testdata/cosmoshub.json"; import { bech32AddressMatcher, deployedErc20, + faucet, fromOneElementArray, getRandomizedHackatom, makeRandomAddress, @@ -22,6 +23,7 @@ import { tendermintIdMatcher, tendermintOptionalIdMatcher, wasmdEnabled, + wasmdEndpoint, } from "./testutils.spec"; import { Coin, @@ -39,17 +41,7 @@ import { const { fromAscii, fromBase64, fromHex, toAscii, toBase64, toHex } = Encoding; -const httpUrl = "http://localhost:1317"; const defaultNetworkId = "testing"; -const faucet = { - mnemonic: - "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone", - pubkey: { - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }, - address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", -}; const emptyAddress = "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k"; const unusedAccount = { address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u", @@ -172,7 +164,7 @@ async function executeContract( describe("RestClient", () => { it("can be constructed", () => { - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); expect(client).toBeTruthy(); }); @@ -181,7 +173,7 @@ describe("RestClient", () => { describe("authAccounts", () => { it("works for unused account without pubkey", async () => { pendingWithoutWasmd(); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const { result } = await client.authAccounts(unusedAccount.address); expect(result).toEqual({ type: "cosmos-sdk/Account", @@ -207,7 +199,7 @@ describe("RestClient", () => { // This fails in the first test run if you forget to run `./scripts/wasmd/init.sh` it("has correct pubkey for faucet", async () => { pendingWithoutWasmd(); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const { result } = await client.authAccounts(faucet.address); expect(result.value).toEqual( jasmine.objectContaining({ @@ -219,7 +211,7 @@ describe("RestClient", () => { // This property is used by CosmWasmClient.getAccount it("returns empty address for non-existent account", async () => { pendingWithoutWasmd(); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const nonExistentAccount = makeRandomAddress(); const { result } = await client.authAccounts(nonExistentAccount); expect(result).toEqual({ @@ -234,7 +226,7 @@ describe("RestClient", () => { describe("blocksLatest", () => { it("works", async () => { pendingWithoutWasmd(); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const response = await client.blocksLatest(); // id @@ -267,7 +259,7 @@ describe("RestClient", () => { describe("blocks", () => { it("works for block by height", async () => { pendingWithoutWasmd(); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const height = parseInt((await client.blocksLatest()).block.header.height, 10); const response = await client.blocks(height - 1); @@ -303,7 +295,7 @@ describe("RestClient", () => { describe("nodeInfo", () => { it("works", async () => { pendingWithoutWasmd(); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const info = await client.nodeInfo(); expect(info.node_info.network).toEqual(defaultNetworkId); }); @@ -325,7 +317,9 @@ describe("RestClient", () => { beforeAll(async () => { if (wasmdEnabled()) { const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); + const client = new SigningCosmWasmClient(wasmdEndpoint, faucet.address, signBytes => + pen.sign(signBytes), + ); const recipient = makeRandomAddress(); const transferAmount = [ @@ -337,7 +331,7 @@ describe("RestClient", () => { const result = await client.sendTokens(recipient, transferAmount); await sleep(50); // wait until tx is indexed - const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); + const txDetails = await new RestClient(wasmdEndpoint).txsById(result.transactionHash); posted = { sender: faucet.address, recipient: recipient, @@ -351,7 +345,7 @@ describe("RestClient", () => { it("can query transactions by height", async () => { pendingWithoutWasmd(); assert(posted); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const result = await client.txsQuery(`tx.height=${posted.height}&limit=26`); expect(parseInt(result.count, 10)).toEqual(1); expect(parseInt(result.limit, 10)).toEqual(26); @@ -364,7 +358,7 @@ describe("RestClient", () => { it("can query transactions by ID", async () => { pendingWithoutWasmd(); assert(posted); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const result = await client.txsQuery(`tx.hash=${posted.hash}&limit=26`); expect(parseInt(result.count, 10)).toEqual(1); expect(parseInt(result.limit, 10)).toEqual(26); @@ -377,7 +371,7 @@ describe("RestClient", () => { it("can query transactions by sender", async () => { pendingWithoutWasmd(); assert(posted); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const result = await client.txsQuery(`message.sender=${posted.sender}&limit=200`); expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(1); expect(parseInt(result.limit, 10)).toEqual(200); @@ -391,7 +385,7 @@ describe("RestClient", () => { it("can query transactions by recipient", async () => { pendingWithoutWasmd(); assert(posted); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const result = await client.txsQuery(`transfer.recipient=${posted.recipient}&limit=200`); expect(parseInt(result.count, 10)).toEqual(1); expect(parseInt(result.limit, 10)).toEqual(200); @@ -406,7 +400,7 @@ describe("RestClient", () => { pending("This combination is broken 🤷‍♂️. Handle client-side at higher level."); pendingWithoutWasmd(); assert(posted); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const hashQuery = `tx.hash=${posted.hash}`; { @@ -433,7 +427,7 @@ describe("RestClient", () => { it("can filter by recipient and tx.minheight", async () => { pendingWithoutWasmd(); assert(posted); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const recipientQuery = `transfer.recipient=${posted.recipient}`; { @@ -460,7 +454,7 @@ describe("RestClient", () => { it("can filter by recipient and tx.maxheight", async () => { pendingWithoutWasmd(); assert(posted); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const recipientQuery = `transfer.recipient=${posted.recipient}`; { @@ -487,7 +481,7 @@ describe("RestClient", () => { it("can query by tags (module + code_id)", async () => { pendingWithoutWasmd(); assert(posted); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const result = await client.txsQuery(`message.module=wasm&message.code_id=${deployedErc20.codeId}`); expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(4); @@ -533,7 +527,7 @@ describe("RestClient", () => { it("can query by tags (module + code_id + action)", async () => { pendingWithoutWasmd(); assert(posted); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); { const uploads = await client.txsQuery( @@ -590,7 +584,7 @@ describe("RestClient", () => { describe("encodeTx", () => { it("works for cosmoshub example", async () => { pendingWithoutWasmd(); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); expect(await client.encodeTx(cosmoshub.tx)).toEqual(fromBase64(cosmoshub.tx_data)); }); }); @@ -625,7 +619,7 @@ describe("RestClient", () => { gas: "890000", }; - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account_number, sequence); @@ -639,7 +633,7 @@ describe("RestClient", () => { it("can upload, instantiate and execute wasm", async () => { pendingWithoutWasmd(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const transferAmount: readonly Coin[] = [ { @@ -713,7 +707,7 @@ describe("RestClient", () => { it("can list upload code", async () => { pendingWithoutWasmd(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); // check with contracts were here first to compare const existingInfos = await client.listCodeInfo(); @@ -753,7 +747,7 @@ describe("RestClient", () => { it("can list contracts and get info", async () => { pendingWithoutWasmd(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const beneficiaryAddress = makeRandomAddress(); const transferAmount: readonly Coin[] = [ { @@ -814,7 +808,7 @@ describe("RestClient", () => { }); describe("contract state", () => { - const client = new RestClient(httpUrl); + const client = new RestClient(wasmdEndpoint); const noContract = makeRandomAddress(); const expectedKey = toAscii("config"); let contractAddress: string | undefined; diff --git a/packages/sdk/src/testutils.spec.ts b/packages/sdk/src/testutils.spec.ts index 78f3c090..5eba32fa 100644 --- a/packages/sdk/src/testutils.spec.ts +++ b/packages/sdk/src/testutils.spec.ts @@ -76,6 +76,18 @@ export const deployedErc20 = { ], }; +export const wasmdEndpoint = "http://localhost:1317"; + +export const faucet = { + mnemonic: + "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone", + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }, + address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", +}; + export function wasmdEnabled(): boolean { return !!process.env.WASMD_ENABLED; } From ca2394657de826738c947f3bd85289f72ef3ae06 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 11:39:21 +0100 Subject: [PATCH 11/13] Test and implement "can post an ERC20 transfer and search for the transaction" --- packages/bcp/src/cosmwasmconnection.spec.ts | 161 ++++++++++++++------ packages/bcp/src/cosmwasmconnection.ts | 40 ++++- 2 files changed, 157 insertions(+), 44 deletions(-) diff --git a/packages/bcp/src/cosmwasmconnection.spec.ts b/packages/bcp/src/cosmwasmconnection.spec.ts index 4355fd57..864f41e5 100644 --- a/packages/bcp/src/cosmwasmconnection.spec.ts +++ b/packages/bcp/src/cosmwasmconnection.spec.ts @@ -589,6 +589,124 @@ describe("CosmWasmConnection", () => { connection.disconnect(); }); + it("can post an ERC20 transfer and search for the transaction", async () => { + pendingWithoutWasmd(); + const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig); + const profile = new UserProfile(); + const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucet.mnemonic)); + const sender = await profile.createIdentity(wallet.id, defaultChainId, faucet.path); + const senderAddress = connection.codec.identityToAddress(sender); + + const recipient = makeRandomAddress(); + const unsigned = await connection.withDefaultFee({ + kind: "bcp/send", + chainId: defaultChainId, + sender: senderAddress, + recipient: recipient, + memo: "My first payment", + amount: { + quantity: "75", + fractionalDigits: 0, + tokenTicker: "ISA" as TokenTicker, + }, + }); + const nonce = await connection.getNonce({ address: senderAddress }); + const signed = await profile.signTransaction(sender, unsigned, connection.codec, nonce); + const postableBytes = connection.codec.bytesToPost(signed); + const response = await connection.postTx(postableBytes); + const { transactionId } = response; + const blockInfo = await response.blockInfo.waitFor(info => !isBlockInfoPending(info)); + expect(blockInfo.state).toEqual(TransactionState.Succeeded); + + // search by id + const byIdResults = await connection.searchTx({ id: transactionId }); + expect(byIdResults.length).toEqual(1); + const byIdResult = byIdResults[0]; + expect(byIdResult.transactionId).toEqual(transactionId); + assert(isConfirmedTransaction(byIdResult), "Expected transaction to succeed"); + assert(byIdResult.log, "Log must be available"); + const [firstByIdlog] = JSON.parse(byIdResult.log); + expect(firstByIdlog.events.length).toEqual(2); + expect(firstByIdlog.events[0].type).toEqual("message"); + expect(firstByIdlog.events[1].type).toEqual("wasm"); + // wasm event attributes added by contract + expect(firstByIdlog.events[1].attributes).toContain({ key: "action", value: "transfer" }); + expect(firstByIdlog.events[1].attributes).toContain({ key: "sender", value: senderAddress }); + expect(firstByIdlog.events[1].attributes).toContain({ key: "recipient", value: recipient }); + // wasm event attributes added wasmd + expect(firstByIdlog.events[1].attributes).toContain({ + key: "contract_address", + value: defaultConfig.erc20Tokens![1].contractAddress, + }); + const byIdTransaction = byIdResult.transaction; + assert(isSendTransaction(byIdTransaction), "Expected send transaction"); + expect(byIdTransaction).toEqual(unsigned); + + // search by sender address + const bySenderResults = await connection.searchTx({ sentFromOrTo: senderAddress }); + expect(bySenderResults).toBeTruthy(); + expect(bySenderResults.length).toBeGreaterThanOrEqual(1); + const bySenderResult = bySenderResults[bySenderResults.length - 1]; + expect(bySenderResult.transactionId).toEqual(transactionId); + assert(isConfirmedTransaction(bySenderResult), "Expected transaction to succeed"); + assert(bySenderResult.log, "Log must be available"); + const [firstBySenderLog] = JSON.parse(bySenderResult.log); + expect(firstBySenderLog.events.length).toEqual(2); + expect(firstBySenderLog.events[0].type).toEqual("message"); + expect(firstBySenderLog.events[1].type).toEqual("wasm"); + // wasm event attributes added by contract + expect(firstBySenderLog.events[1].attributes).toContain({ key: "action", value: "transfer" }); + expect(firstBySenderLog.events[1].attributes).toContain({ key: "sender", value: senderAddress }); + expect(firstBySenderLog.events[1].attributes).toContain({ key: "recipient", value: recipient }); + // wasm event attributes added wasmd + expect(firstBySenderLog.events[1].attributes).toContain({ + key: "contract_address", + value: defaultConfig.erc20Tokens![1].contractAddress, + }); + const bySenderTransaction = bySenderResult.transaction; + assert(isSendTransaction(bySenderTransaction), "Expected send transaction"); + expect(bySenderTransaction).toEqual(unsigned); + + // search by recipient address + const byRecipientResults = await connection.searchTx({ sentFromOrTo: recipient }); + expect(byRecipientResults.length).toBeGreaterThanOrEqual(1); + const byRecipientResult = byRecipientResults[byRecipientResults.length - 1]; + expect(byRecipientResult.transactionId).toEqual(transactionId); + assert(isConfirmedTransaction(byRecipientResult), "Expected transaction to succeed"); + assert(byRecipientResult.log, "Log must be available"); + const [firstByRecipientLog] = JSON.parse(bySenderResult.log); + expect(firstByRecipientLog.events.length).toEqual(2); + expect(firstByRecipientLog.events[0].type).toEqual("message"); + expect(firstByRecipientLog.events[1].type).toEqual("wasm"); + // wasm event attributes added by contract + expect(firstByRecipientLog.events[1].attributes).toContain({ key: "action", value: "transfer" }); + expect(firstByRecipientLog.events[1].attributes).toContain({ key: "sender", value: senderAddress }); + expect(firstByRecipientLog.events[1].attributes).toContain({ key: "recipient", value: recipient }); + // wasm event attributes added wasmd + expect(firstByRecipientLog.events[1].attributes).toContain({ + key: "contract_address", + value: defaultConfig.erc20Tokens![1].contractAddress, + }); + const byRecipeintTransaction = byRecipientResult.transaction; + assert(isSendTransaction(byRecipeintTransaction), "Expected send transaction"); + expect(byRecipeintTransaction).toEqual(unsigned); + + // search by height + const heightResults = await connection.searchTx({ height: byIdResult.height }); + expect(heightResults.length).toEqual(1); + const heightResult = heightResults[0]; + expect(heightResult.transactionId).toEqual(transactionId); + assert(isConfirmedTransaction(heightResult), "Expected transaction to succeed"); + assert(heightResult.log, "Log must be available"); + const [firstHeightLog] = JSON.parse(heightResult.log); + expect(firstHeightLog.events.length).toEqual(2); + const heightTransaction = heightResult.transaction; + assert(isSendTransaction(heightTransaction), "Expected send transaction"); + expect(heightTransaction).toEqual(unsigned); + + connection.disconnect(); + }); + it("can search by minHeight and maxHeight", async () => { pendingWithoutWasmd(); const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig); @@ -980,47 +1098,4 @@ describe("CosmWasmConnection", () => { })().catch(done.fail); }); }); - - describe("integration tests", () => { - it("can send ERC20 tokens", async () => { - pendingWithoutWasmd(); - const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig); - const profile = new UserProfile(); - const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucet.mnemonic)); - const sender = await profile.createIdentity(wallet.id, defaultChainId, faucet.path); - const senderAddress = connection.codec.identityToAddress(sender); - const recipient = makeRandomAddress(); - - const unsigned = await connection.withDefaultFee({ - kind: "bcp/send", - chainId: defaultChainId, - sender: senderAddress, - recipient: recipient, - memo: "My first payment", - amount: { - quantity: "75", - fractionalDigits: 0, - tokenTicker: "ISA" as TokenTicker, - }, - }); - const nonce = await connection.getNonce({ address: senderAddress }); - const signed = await profile.signTransaction(sender, unsigned, connection.codec, nonce); - const postableBytes = connection.codec.bytesToPost(signed); - const response = await connection.postTx(postableBytes); - const blockInfo = await response.blockInfo.waitFor(info => !isBlockInfoPending(info)); - expect(blockInfo.state).toEqual(TransactionState.Succeeded); - - const recipientAccount = await connection.getAccount({ address: recipient }); - assert(recipientAccount, "Recipient account must have ISA tokens"); - expect(recipientAccount.balance).toEqual([ - { - tokenTicker: "ISA" as TokenTicker, - quantity: "75", - fractionalDigits: 0, - }, - ]); - - connection.disconnect(); - }); - }); }); diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index f26fa724..a7054e82 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -60,6 +60,16 @@ function isDefined(value: X | undefined): value is X { return value !== undefined; } +function deduplicate(input: ReadonlyArray, comparator: (a: T, b: T) => number): ReadonlyArray { + const out = new Array(); + for (const element of input) { + if (!out.find(o => comparator(o, element) === 0)) { + out.push(element); + } + } + return out; +} + /** Account and undefined are valid events. The third option means no event fired yet */ type LastWatchAccountEvent = Account | undefined | "no_event_fired_yet"; @@ -324,7 +334,35 @@ export class CosmWasmConnection implements BlockchainConnection { } else if (height) { txs = await this.cosmWasmClient.searchTx({ height: height }, filter); } else if (sentFromOrTo) { - txs = await this.cosmWasmClient.searchTx({ sentFromOrTo: sentFromOrTo }, filter); + const pendingRequests = new Array>(); + pendingRequests.push(this.cosmWasmClient.searchTx({ sentFromOrTo: sentFromOrTo }, filter)); + for (const contract of this.erc20Tokens.map(token => token.contractAddress)) { + const searchBySender = [ + { + key: "wasm.contract_address", + value: contract, + }, + { + key: "wasm.sender", + value: sentFromOrTo, + }, + ]; + const searchByRecipient = [ + { + key: "wasm.contract_address", + value: contract, + }, + { + key: "wasm.recipient", + value: sentFromOrTo, + }, + ]; + pendingRequests.push(this.cosmWasmClient.searchTx({ tags: searchBySender }, filter)); + pendingRequests.push(this.cosmWasmClient.searchTx({ tags: searchByRecipient }, filter)); + } + const responses = await Promise.all(pendingRequests); + const allResults = responses.reduce((accumulator, results) => accumulator.concat(results), []); + txs = deduplicate(allResults, (a, b) => a.txhash.localeCompare(b.txhash)); } else { throw new Error("Unsupported query"); } From 7576033134ee744e2d7739d05cedcd98c0bce1e8 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 12:01:03 +0100 Subject: [PATCH 12/13] Fix order when merging results --- packages/bcp/src/cosmwasmconnection.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index a7054e82..648ea7a0 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -60,7 +60,7 @@ function isDefined(value: X | undefined): value is X { return value !== undefined; } -function deduplicate(input: ReadonlyArray, comparator: (a: T, b: T) => number): ReadonlyArray { +function deduplicate(input: ReadonlyArray, comparator: (a: T, b: T) => number): Array { const out = new Array(); for (const element of input) { if (!out.find(o => comparator(o, element) === 0)) { @@ -70,6 +70,15 @@ function deduplicate(input: ReadonlyArray, comparator: (a: T, b: T) => num return out; } +/** Compares transaxtion by height. If the height is equal, compare by hash to ensure deterministic order */ +function compareByHeightAndHash(a: TxsResponse, b: TxsResponse): number { + if (a.height === b.height) { + return a.txhash.localeCompare(b.txhash); + } else { + return parseInt(a.height, 10) - parseInt(b.height, 10); + } +} + /** Account and undefined are valid events. The third option means no event fired yet */ type LastWatchAccountEvent = Account | undefined | "no_event_fired_yet"; @@ -362,7 +371,7 @@ export class CosmWasmConnection implements BlockchainConnection { } const responses = await Promise.all(pendingRequests); const allResults = responses.reduce((accumulator, results) => accumulator.concat(results), []); - txs = deduplicate(allResults, (a, b) => a.txhash.localeCompare(b.txhash)); + txs = deduplicate(allResults, (a, b) => a.txhash.localeCompare(b.txhash)).sort(compareByHeightAndHash); } else { throw new Error("Unsupported query"); } From b6045e00bbef39a64c2ccc83d9735859640b1d1e Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 17:01:11 +0100 Subject: [PATCH 13/13] Add module filter in receivedQuery --- packages/sdk/src/cosmwasmclient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index 5ce22c5f..e07ac6dc 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -176,7 +176,7 @@ export class CosmWasmClient { } else if (isSearchBySentFromOrToQuery(query)) { // We cannot get both in one request (see https://github.com/cosmos/gaia/issues/75) const sentQuery = withFilters(`message.module=bank&message.sender=${query.sentFromOrTo}`); - const receivedQuery = withFilters(`transfer.recipient=${query.sentFromOrTo}`); + const receivedQuery = withFilters(`message.module=bank&transfer.recipient=${query.sentFromOrTo}`); const sent = await this.txsQuery(sentQuery); const received = await this.txsQuery(receivedQuery);