From 767623f671ca4f8f05d15f23e47b6d0de712d105 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Fri, 11 Dec 2020 16:28:11 +0000 Subject: [PATCH] cosmwasm-stargate: Add tests for new searchTx queries --- .../src/cosmwasmclient.searchtx.spec.ts | 360 ++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 packages/cosmwasm-stargate/src/cosmwasmclient.searchtx.spec.ts diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.searchtx.spec.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.searchtx.spec.ts new file mode 100644 index 00000000..0d13a034 --- /dev/null +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.searchtx.spec.ts @@ -0,0 +1,360 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { fromBase64, toBase64 } from "@cosmjs/encoding"; +import { Coin, coins } from "@cosmjs/launchpad"; +import { + DirectSecp256k1HdWallet, + encodePubkey, + makeAuthInfoBytes, + makeSignDoc, + Registry, +} from "@cosmjs/proto-signing"; +import { BroadcastTxResponse, codec, isBroadcastTxFailure, isBroadcastTxSuccess } from "@cosmjs/stargate"; +import { assert, sleep } from "@cosmjs/utils"; + +import { CosmWasmClient } from "./cosmwasmclient"; +import { + alice, + fromOneElementArray, + makeRandomAddress, + pendingWithoutWasmd, + wasmd, + wasmdEnabled, +} from "./testutils.spec"; + +const { Tx, TxRaw } = codec.cosmos.tx.v1beta1; + +interface TestTxSend { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + readonly height: number; + readonly tx: Uint8Array; +} + +async function sendTokens( + client: CosmWasmClient, + registry: Registry, + wallet: DirectSecp256k1HdWallet, + recipient: string, + amount: readonly Coin[], + memo: string, +): Promise<{ + readonly broadcastResponse: BroadcastTxResponse; + readonly tx: Uint8Array; +}> { + const [{ address: walletAddress, pubkey: pubkeyBytes }] = await wallet.getAccounts(); + const pubkey = encodePubkey({ + type: "tendermint/PubKeySecp256k1", + value: toBase64(pubkeyBytes), + }); + const txBodyFields = { + typeUrl: "/cosmos.tx.v1beta1.TxBody", + value: { + messages: [ + { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress: walletAddress, + toAddress: recipient, + amount: amount, + }, + }, + ], + memo: memo, + }, + }; + const txBodyBytes = registry.encode(txBodyFields); + const { accountNumber, sequence } = (await client.getSequence(walletAddress))!; + const feeAmount = [ + { + amount: "2000", + denom: "ucosm", + }, + ]; + const gasLimit = 200000; + const authInfoBytes = makeAuthInfoBytes([pubkey], feeAmount, gasLimit, sequence); + + const chainId = await client.getChainId(); + const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber); + const { signature } = await wallet.signDirect(walletAddress, signDoc); + const txRaw = TxRaw.create({ + bodyBytes: txBodyBytes, + authInfoBytes: authInfoBytes, + signatures: [fromBase64(signature.signature)], + }); + const txRawBytes = Uint8Array.from(TxRaw.encode(txRaw).finish()); + const broadcastResponse = await client.broadcastTx(txRawBytes); + return { + broadcastResponse: broadcastResponse, + tx: txRawBytes, + }; +} + +describe("CosmWasmClient.getTx and .searchTx", () => { + const registry = new Registry(); + + let sendUnsuccessful: TestTxSend | undefined; + let sendSuccessful: TestTxSend | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); + const client = await CosmWasmClient.connect(wasmd.endpoint); + const unsuccessfulRecipient = makeRandomAddress(); + const successfulRecipient = makeRandomAddress(); + + const unsuccessfulResult = await sendTokens( + client, + registry, + wallet, + unsuccessfulRecipient, + coins(123456700000000, "ucosm"), + "Sending more than I can afford", + ); + if (isBroadcastTxFailure(unsuccessfulResult.broadcastResponse)) { + sendUnsuccessful = { + sender: alice.address0, + recipient: unsuccessfulRecipient, + hash: unsuccessfulResult.broadcastResponse.transactionHash, + height: unsuccessfulResult.broadcastResponse.height, + tx: unsuccessfulResult.tx, + }; + } + const successfulResult = await sendTokens( + client, + registry, + wallet, + successfulRecipient, + coins(1234567, "ucosm"), + "Something I can afford", + ); + if (isBroadcastTxSuccess(successfulResult.broadcastResponse)) { + sendSuccessful = { + sender: alice.address0, + recipient: successfulRecipient, + hash: successfulResult.broadcastResponse.transactionHash, + height: successfulResult.broadcastResponse.height, + tx: successfulResult.tx, + }; + } + + await sleep(75); // wait until transactions are indexed + } + }); + + describe("getTx", () => { + it("can get successful tx by ID", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = await CosmWasmClient.connect(wasmd.endpoint); + const result = await client.getTx(sendSuccessful.hash); + expect(result).toEqual( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + code: 0, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can get unsuccessful tx by ID", async () => { + pendingWithoutWasmd(); + assert(sendUnsuccessful, "value must be set in beforeAll()"); + const client = await CosmWasmClient.connect(wasmd.endpoint); + const result = await client.getTx(sendUnsuccessful.hash); + expect(result).toEqual( + jasmine.objectContaining({ + height: sendUnsuccessful.height, + hash: sendUnsuccessful.hash, + code: 5, + tx: sendUnsuccessful.tx, + }), + ); + }); + + it("can get by ID (non existent)", async () => { + pendingWithoutWasmd(); + const client = await CosmWasmClient.connect(wasmd.endpoint); + const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000"; + const result = await client.getTx(nonExistentId); + expect(result).toBeNull(); + }); + }); + + describe("with SearchByHeightQuery", () => { + it("can search successful tx by height", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = await CosmWasmClient.connect(wasmd.endpoint); + const result = await client.searchTx({ height: sendSuccessful.height }); + expect(result.length).toBeGreaterThanOrEqual(1); + expect(result).toContain( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + code: 0, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can search unsuccessful tx by height", async () => { + pendingWithoutWasmd(); + assert(sendUnsuccessful, "value must be set in beforeAll()"); + const client = await CosmWasmClient.connect(wasmd.endpoint); + const result = await client.searchTx({ height: sendUnsuccessful.height }); + expect(result.length).toBeGreaterThanOrEqual(1); + expect(result).toContain( + jasmine.objectContaining({ + height: sendUnsuccessful.height, + hash: sendUnsuccessful.hash, + code: 5, + tx: sendUnsuccessful.tx, + }), + ); + }); + }); + + describe("with SearchBySentFromOrToQuery", () => { + it("can search by sender", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = await CosmWasmClient.connect(wasmd.endpoint); + const results = await client.searchTx({ sentFromOrTo: sendSuccessful.sender }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const tx = Tx.decode(result.tx); + const filteredMsgs = tx.body!.messages!.filter(({ type_url: typeUrl, value }) => { + if (typeUrl !== "/cosmos.bank.v1beta1.MsgSend") return false; + const decoded = registry.decode({ typeUrl: typeUrl, value: value! }); + return decoded.fromAddress === sendSuccessful?.sender; + }); + expect(filteredMsgs.length).toBeGreaterThanOrEqual(1); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can search by recipient", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = await CosmWasmClient.connect(wasmd.endpoint); + const results = await client.searchTx({ sentFromOrTo: sendSuccessful.recipient }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const tx = Tx.decode(result.tx); + const filteredMsgs = tx.body!.messages!.filter(({ type_url: typeUrl, value }) => { + if (typeUrl !== "/cosmos.bank.v1beta1.MsgSend") return false; + const decoded = registry.decode({ typeUrl: typeUrl, value: value! }); + return decoded.toAddress === sendSuccessful?.recipient; + }); + expect(filteredMsgs.length).toBeGreaterThanOrEqual(1); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can search by recipient and filter by minHeight", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful); + const client = await CosmWasmClient.connect(wasmd.endpoint); + const query = { sentFromOrTo: sendSuccessful.recipient }; + + { + const result = await client.searchTx(query, { minHeight: 0 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: sendSuccessful.height - 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: sendSuccessful.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: sendSuccessful.height + 1 }); + expect(result.length).toEqual(0); + } + }); + + it("can search by recipient and filter by maxHeight", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful); + const client = await CosmWasmClient.connect(wasmd.endpoint); + const query = { sentFromOrTo: sendSuccessful.recipient }; + + { + const result = await client.searchTx(query, { maxHeight: 9999999999999 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: sendSuccessful.height + 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: sendSuccessful.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: sendSuccessful.height - 1 }); + expect(result.length).toEqual(0); + } + }); + }); + + describe("with SearchByTagsQuery", () => { + it("can search by transfer.recipient", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = await CosmWasmClient.connect(wasmd.endpoint); + const results = await client.searchTx({ + tags: [{ key: "transfer.recipient", value: sendSuccessful.recipient }], + }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const tx = Tx.decode(result.tx); + const { type_url: typeUrl, value } = fromOneElementArray(tx.body!.messages!); + expect(typeUrl).toEqual("/cosmos.bank.v1beta1.MsgSend"); + const decoded = registry.decode({ typeUrl: typeUrl!, value: value! }); + expect(decoded.toAddress).toEqual(sendSuccessful.recipient); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + tx: sendSuccessful.tx, + }), + ); + }); + }); +});