diff --git a/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts b/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts index f6bef50d..9ed9a979 100644 --- a/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts +++ b/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts @@ -1,9 +1,8 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { Uint53 } from "@cosmjs/math"; import { Coin, CosmosSdkTx, isMsgSend, makeSignBytes, MsgSend, Secp256k1Pen } from "@cosmjs/sdk38"; import { assert, sleep } from "@cosmjs/utils"; -import { CosmWasmClient } from "./cosmwasmclient"; +import { CosmWasmClient, isPostTxFailure } from "./cosmwasmclient"; import { isMsgExecuteContract, isMsgInstantiateContract } from "./msgs"; import { RestClient } from "./restclient"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; @@ -113,19 +112,13 @@ describe("CosmWasmClient.searchTx", () => { }, }; const transactionId = await client.getIdentifier(tx); - try { - await client.postTx(tx.value); - } catch (error) { - // postTx() throws on execution failures, which is a questionable design. Ignore for now. - // console.log(error); - const errorMessage: string = error.toString(); - const [_, heightMatch] = errorMessage.match(/at height ([0-9]+)/) || ["", ""]; - + const result = await client.postTx(tx.value); + if (isPostTxFailure(result)) { sendUnsuccessful = { sender: alice.address0, recipient: recipient, hash: transactionId, - height: Uint53.fromString(heightMatch).toNumber(), + height: result.height, tx: tx, }; } diff --git a/packages/cosmwasm/src/cosmwasmclient.spec.ts b/packages/cosmwasm/src/cosmwasmclient.spec.ts index b28fe430..1898982e 100644 --- a/packages/cosmwasm/src/cosmwasmclient.spec.ts +++ b/packages/cosmwasm/src/cosmwasmclient.spec.ts @@ -5,7 +5,7 @@ import { makeSignBytes, MsgSend, Secp256k1Pen, StdFee } from "@cosmjs/sdk38"; import { assert, sleep } from "@cosmjs/utils"; import { ReadonlyDate } from "readonly-date"; -import { Code, CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient"; +import { Code, CosmWasmClient, isPostTxFailure, PrivateCosmWasmClient } from "./cosmwasmclient"; import { findAttribute } from "./logs"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import cosmoshub from "./testdata/cosmoshub.json"; @@ -242,7 +242,9 @@ describe("CosmWasmClient", () => { memo: memo, signatures: [signature], }; - const { logs, transactionHash } = await client.postTx(signedTx); + const result = await client.postTx(signedTx); + assert(!isPostTxFailure(result)); + const { logs, transactionHash } = result; const amountAttr = findAttribute(logs, "transfer", "amount"); expect(amountAttr.value).toEqual("1234567ucosm"); expect(transactionHash).toMatch(/^[0-9A-F]{64}$/); diff --git a/packages/cosmwasm/src/cosmwasmclient.ts b/packages/cosmwasm/src/cosmwasmclient.ts index 886b860a..45f88de7 100644 --- a/packages/cosmwasm/src/cosmwasmclient.ts +++ b/packages/cosmwasm/src/cosmwasmclient.ts @@ -1,5 +1,6 @@ import { Sha256 } from "@cosmjs/crypto"; import { fromBase64, fromHex, toHex } from "@cosmjs/encoding"; +import { Uint53 } from "@cosmjs/math"; import { BroadcastMode, Coin, @@ -28,11 +29,26 @@ export interface Account { readonly sequence: number; } -export interface PostTxResult { +export interface PostTxFailure { + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; + readonly height: number; + readonly code: number; + readonly rawLog: string; +} + +export interface PostTxSuccess { readonly logs: readonly Log[]; readonly rawLog: string; /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ readonly transactionHash: string; + readonly data?: Uint8Array; +} + +export type PostTxResult = PostTxSuccess | PostTxFailure; + +export function isPostTxFailure(postTxResult: PostTxResult): postTxResult is PostTxFailure { + return !!(postTxResult as PostTxFailure).code; } export interface SearchByIdQuery { @@ -294,17 +310,19 @@ export class CosmWasmClient { throw new Error("Received ill-formatted txhash. Must be non-empty upper-case hex"); } - if (result.code) { - throw new Error( - `Error when posting tx ${result.txhash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.raw_log}`, - ); - } - - return { - logs: result.logs ? parseLogs(result.logs) : [], - rawLog: result.raw_log || "", - transactionHash: result.txhash, - }; + return result.code !== undefined + ? { + height: Uint53.fromString(result.height).toNumber(), + transactionHash: result.txhash, + code: result.code, + rawLog: result.raw_log || "", + } + : { + logs: result.logs ? parseLogs(result.logs) : [], + rawLog: result.raw_log || "", + transactionHash: result.txhash, + data: result.data ? fromHex(result.data) : undefined, + }; } public async getCodes(): Promise { diff --git a/packages/cosmwasm/src/restclient.spec.ts b/packages/cosmwasm/src/restclient.spec.ts index 007291b1..39a2a765 100644 --- a/packages/cosmwasm/src/restclient.spec.ts +++ b/packages/cosmwasm/src/restclient.spec.ts @@ -19,6 +19,7 @@ import { import { assert, sleep } from "@cosmjs/utils"; import { ReadonlyDate } from "readonly-date"; +import { isPostTxFailure } from "./cosmwasmclient"; import { findAttribute, parseLogs } from "./logs"; import { isMsgInstantiateContract, @@ -400,12 +401,8 @@ describe("RestClient", () => { signatures: [signature], }; const transactionId = await client.getIdentifier({ type: "cosmos-sdk/StdTx", value: signedTx }); - try { - await client.postTx(signedTx); - } catch (error) { - // postTx() throws on execution failures, which is a questionable design. Ignore for now. - // console.log(error); - } + const result = await client.postTx(signedTx); + assert(isPostTxFailure(result)); unsuccessful = { sender: alice.address0, recipient: recipient, @@ -858,7 +855,6 @@ describe("RestClient", () => { signatures: [signature1, signature2, signature3], }; const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); expect(postResult.code).toEqual(4); expect(postResult.raw_log).toContain("wrong number of signers"); }); @@ -918,7 +914,6 @@ describe("RestClient", () => { signatures: [signature1], }; const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); expect(postResult.code).toBeUndefined(); }); @@ -982,7 +977,6 @@ describe("RestClient", () => { signatures: [signature2, signature1], }; const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); expect(postResult.code).toBeUndefined(); await sleep(500); @@ -1051,7 +1045,6 @@ describe("RestClient", () => { signatures: [signature2, signature1], }; const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); expect(postResult.code).toEqual(8); }); @@ -1115,7 +1108,6 @@ describe("RestClient", () => { signatures: [signature1, signature2], }; const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); expect(postResult.code).toEqual(8); }); diff --git a/packages/cosmwasm/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm/src/signingcosmwasmclient.spec.ts index aab2c4b2..92a1489a 100644 --- a/packages/cosmwasm/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm/src/signingcosmwasmclient.spec.ts @@ -3,7 +3,7 @@ import { toHex } from "@cosmjs/encoding"; import { Coin, Secp256k1Pen } from "@cosmjs/sdk38"; import { assert } from "@cosmjs/utils"; -import { PrivateCosmWasmClient } from "./cosmwasmclient"; +import { isPostTxFailure, PrivateCosmWasmClient } from "./cosmwasmclient"; import { RestClient } from "./restclient"; import { SigningCosmWasmClient, UploadMeta } from "./signingcosmwasmclient"; import { alice, getHackatom, makeRandomAddress, pendingWithoutWasmd } from "./testutils.spec"; @@ -205,6 +205,7 @@ describe("SigningCosmWasmClient", () => { // send const result = await client.sendTokens(beneficiaryAddress, transferAmount, "for dinner"); + assert(!isPostTxFailure(result)); const [firstLog] = result.logs; expect(firstLog).toBeTruthy(); diff --git a/packages/cosmwasm/src/signingcosmwasmclient.ts b/packages/cosmwasm/src/signingcosmwasmclient.ts index 7dd234fe..3e52b236 100644 --- a/packages/cosmwasm/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm/src/signingcosmwasmclient.ts @@ -4,7 +4,14 @@ import { BroadcastMode, Coin, coins, makeSignBytes, MsgSend, StdFee, StdSignatur import pako from "pako"; import { isValidBuilder } from "./builder"; -import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; +import { + Account, + CosmWasmClient, + GetNonceResult, + isPostTxFailure, + PostTxFailure, + PostTxResult, +} from "./cosmwasmclient"; import { findAttribute, Log } from "./logs"; import { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } from "./msgs"; @@ -84,6 +91,10 @@ export interface ExecuteResult { readonly transactionHash: string; } +function createPostTxErrorMessage(result: PostTxFailure): string { + return `Error when posting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`; +} + export class SigningCosmWasmClient extends CosmWasmClient { public readonly senderAddress: string; @@ -154,6 +165,9 @@ export class SigningCosmWasmClient extends CosmWasmClient { }; const result = await this.postTx(signedTx); + if (isPostTxFailure(result)) { + throw new Error(createPostTxErrorMessage(result)); + } const codeIdAttr = findAttribute(result.logs, "message", "code_id"); return { originalSize: wasmCode.length, @@ -200,6 +214,9 @@ export class SigningCosmWasmClient extends CosmWasmClient { }; const result = await this.postTx(signedTx); + if (isPostTxFailure(result)) { + throw new Error(createPostTxErrorMessage(result)); + } const contractAddressAttr = findAttribute(result.logs, "message", "contract_address"); return { contractAddress: contractAddressAttr.value, @@ -237,6 +254,9 @@ export class SigningCosmWasmClient extends CosmWasmClient { }; const result = await this.postTx(signedTx); + if (isPostTxFailure(result)) { + throw new Error(createPostTxErrorMessage(result)); + } return { logs: result.logs, transactionHash: result.transactionHash, diff --git a/packages/cosmwasm/types/cosmwasmclient.d.ts b/packages/cosmwasm/types/cosmwasmclient.d.ts index f423bae4..0dad3fa0 100644 --- a/packages/cosmwasm/types/cosmwasmclient.d.ts +++ b/packages/cosmwasm/types/cosmwasmclient.d.ts @@ -14,12 +14,22 @@ export interface Account { readonly accountNumber: number; readonly sequence: number; } -export interface PostTxResult { +export interface PostTxFailure { + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; + readonly height: number; + readonly code: number; + readonly rawLog: string; +} +export interface PostTxSuccess { readonly logs: readonly Log[]; readonly rawLog: string; /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ readonly transactionHash: string; + readonly data?: Uint8Array; } +export declare type PostTxResult = PostTxSuccess | PostTxFailure; +export declare function isPostTxFailure(postTxResult: PostTxResult): postTxResult is PostTxFailure; export interface SearchByIdQuery { readonly id: string; } diff --git a/packages/sdk38/src/cosmosclient.searchtx.spec.ts b/packages/sdk38/src/cosmosclient.searchtx.spec.ts index c75c5e88..b1c75d60 100644 --- a/packages/sdk38/src/cosmosclient.searchtx.spec.ts +++ b/packages/sdk38/src/cosmosclient.searchtx.spec.ts @@ -1,9 +1,8 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { Uint53 } from "@cosmjs/math"; import { assert, sleep } from "@cosmjs/utils"; import { Coin } from "./coins"; -import { CosmosClient } from "./cosmosclient"; +import { CosmosClient, isPostTxFailure } from "./cosmosclient"; import { makeSignBytes } from "./encoding"; import { Secp256k1Pen } from "./pen"; import { RestClient } from "./restclient"; @@ -105,19 +104,13 @@ describe("CosmosClient.searchTx", () => { }, }; const transactionId = await client.getIdentifier(tx); - try { - await client.postTx(tx.value); - } catch (error) { - // postTx() throws on execution failures, which is a questionable design. Ignore for now. - // console.log(error); - const errorMessage: string = error.toString(); - const [_, heightMatch] = errorMessage.match(/at height ([0-9]+)/) || ["", ""]; - + const result = await client.postTx(tx.value); + if (isPostTxFailure(result)) { sendUnsuccessful = { sender: faucet.address, recipient: recipient, hash: transactionId, - height: Uint53.fromString(heightMatch).toNumber(), + height: result.height, tx: tx, }; } diff --git a/packages/sdk38/src/cosmosclient.spec.ts b/packages/sdk38/src/cosmosclient.spec.ts index 3f777899..47433ee9 100644 --- a/packages/sdk38/src/cosmosclient.spec.ts +++ b/packages/sdk38/src/cosmosclient.spec.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { sleep } from "@cosmjs/utils"; +import { assert, sleep } from "@cosmjs/utils"; import { ReadonlyDate } from "readonly-date"; -import { CosmosClient, PrivateCosmWasmClient } from "./cosmosclient"; +import { CosmosClient, isPostTxFailure, PrivateCosmWasmClient } from "./cosmosclient"; import { makeSignBytes } from "./encoding"; import { findAttribute } from "./logs"; import { Secp256k1Pen } from "./pen"; @@ -230,7 +230,9 @@ describe("CosmosClient", () => { memo: memo, signatures: [signature], }; - const { logs, transactionHash } = await client.postTx(signedTx); + const txResult = await client.postTx(signedTx); + assert(!isPostTxFailure(txResult)); + const { logs, transactionHash } = txResult; const amountAttr = findAttribute(logs, "transfer", "amount"); expect(amountAttr.value).toEqual("1234567ucosm"); expect(transactionHash).toMatch(/^[0-9A-F]{64}$/); diff --git a/packages/sdk38/src/cosmosclient.ts b/packages/sdk38/src/cosmosclient.ts index 4c42f385..5b43dc97 100644 --- a/packages/sdk38/src/cosmosclient.ts +++ b/packages/sdk38/src/cosmosclient.ts @@ -1,5 +1,6 @@ import { Sha256 } from "@cosmjs/crypto"; -import { fromBase64, toHex } from "@cosmjs/encoding"; +import { fromBase64, fromHex, toHex } from "@cosmjs/encoding"; +import { Uint53 } from "@cosmjs/math"; import { Coin } from "./coins"; import { Log, parseLogs } from "./logs"; @@ -21,11 +22,26 @@ export interface Account { readonly sequence: number; } -export interface PostTxResult { +export interface PostTxFailure { + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; + readonly height: number; + readonly code: number; + readonly rawLog: string; +} + +export interface PostTxSuccess { readonly logs: readonly Log[]; readonly rawLog: string; /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ readonly transactionHash: string; + readonly data?: Uint8Array; +} + +export type PostTxResult = PostTxSuccess | PostTxFailure; + +export function isPostTxFailure(postTxResult: PostTxResult): postTxResult is PostTxFailure { + return !!(postTxResult as PostTxFailure).code; } export interface SearchByIdQuery { @@ -276,17 +292,19 @@ export class CosmosClient { throw new Error("Received ill-formatted txhash. Must be non-empty upper-case hex"); } - if (result.code) { - throw new Error( - `Error when posting tx ${result.txhash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.raw_log}`, - ); - } - - return { - logs: result.logs ? parseLogs(result.logs) : [], - rawLog: result.raw_log || "", - transactionHash: result.txhash, - }; + return result.code !== undefined + ? { + height: Uint53.fromString(result.height).toNumber(), + transactionHash: result.txhash, + code: result.code, + rawLog: result.raw_log || "", + } + : { + logs: result.logs ? parseLogs(result.logs) : [], + rawLog: result.raw_log || "", + transactionHash: result.txhash, + data: result.data ? fromHex(result.data) : undefined, + }; } private async txsQuery(query: string): Promise { diff --git a/packages/sdk38/src/restclient.spec.ts b/packages/sdk38/src/restclient.spec.ts index 92d3f0ed..bdaea11a 100644 --- a/packages/sdk38/src/restclient.spec.ts +++ b/packages/sdk38/src/restclient.spec.ts @@ -4,6 +4,7 @@ import { assert, sleep } from "@cosmjs/utils"; import { ReadonlyDate } from "readonly-date"; import { rawSecp256k1PubkeyToAddress } from "./address"; +import { isPostTxFailure } from "./cosmosclient"; import { makeSignBytes } from "./encoding"; import { parseLogs } from "./logs"; import { makeCosmoshubPath, Secp256k1Pen } from "./pen"; @@ -275,12 +276,8 @@ describe("RestClient", () => { signatures: [signature], }; const transactionId = await client.getIdentifier({ type: "cosmos-sdk/StdTx", value: signedTx }); - try { - await client.postTx(signedTx); - } catch (error) { - // postTx() throws on execution failures, which is a questionable design. Ignore for now. - // console.log(error); - } + const result = await client.postTx(signedTx); + assert(isPostTxFailure(result)); unsuccessful = { sender: faucet.address, recipient: recipient, @@ -631,7 +628,6 @@ describe("RestClient", () => { signatures: [signature1, signature2, signature3], }; const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); expect(postResult.code).toEqual(4); expect(postResult.raw_log).toContain("wrong number of signers"); }); @@ -691,7 +687,6 @@ describe("RestClient", () => { signatures: [signature1], }; const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); expect(postResult.code).toBeUndefined(); }); @@ -755,7 +750,6 @@ describe("RestClient", () => { signatures: [signature2, signature1], }; const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); expect(postResult.code).toBeUndefined(); await sleep(500); @@ -824,7 +818,6 @@ describe("RestClient", () => { signatures: [signature2, signature1], }; const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); expect(postResult.code).toEqual(8); }); @@ -888,7 +881,6 @@ describe("RestClient", () => { signatures: [signature1, signature2], }; const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); expect(postResult.code).toEqual(8); }); }); diff --git a/packages/sdk38/src/signingcosmosclient.spec.ts b/packages/sdk38/src/signingcosmosclient.spec.ts index f2c186ba..258c4f76 100644 --- a/packages/sdk38/src/signingcosmosclient.spec.ts +++ b/packages/sdk38/src/signingcosmosclient.spec.ts @@ -1,7 +1,7 @@ import { assert } from "@cosmjs/utils"; import { Coin } from "./coins"; -import { PrivateCosmWasmClient } from "./cosmosclient"; +import { isPostTxFailure, PrivateCosmWasmClient } from "./cosmosclient"; import { Secp256k1Pen } from "./pen"; import { SigningCosmosClient } from "./signingcosmosclient"; import { makeRandomAddress, pendingWithoutWasmd } from "./testutils.spec"; @@ -66,6 +66,7 @@ describe("SigningCosmosClient", () => { // send const result = await client.sendTokens(beneficiaryAddress, transferAmount, "for dinner"); + assert(!isPostTxFailure(result)); const [firstLog] = result.logs; expect(firstLog).toBeTruthy(); diff --git a/packages/sdk38/types/cosmosclient.d.ts b/packages/sdk38/types/cosmosclient.d.ts index 0af6120a..4749f6b9 100644 --- a/packages/sdk38/types/cosmosclient.d.ts +++ b/packages/sdk38/types/cosmosclient.d.ts @@ -14,12 +14,22 @@ export interface Account { readonly accountNumber: number; readonly sequence: number; } -export interface PostTxResult { +export interface PostTxFailure { + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; + readonly height: number; + readonly code: number; + readonly rawLog: string; +} +export interface PostTxSuccess { readonly logs: readonly Log[]; readonly rawLog: string; /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ readonly transactionHash: string; + readonly data?: Uint8Array; } +export declare type PostTxResult = PostTxSuccess | PostTxFailure; +export declare function isPostTxFailure(postTxResult: PostTxResult): postTxResult is PostTxFailure; export interface SearchByIdQuery { readonly id: string; }