diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.ts index 1c5c252e..a48d4f15 100644 --- a/packages/cosmwasm-stargate/src/cosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.ts @@ -19,7 +19,9 @@ import { SequenceResponse, setupAuthExtension, setupBankExtension, + setupTxExtension, TimeoutError, + TxExtension, } from "@cosmjs/stargate"; import { Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; import { assert, sleep } from "@cosmjs/utils"; @@ -75,12 +77,16 @@ export interface ContractCodeHistoryEntry { /** Use for testing only */ export interface PrivateCosmWasmClient { readonly tmClient: Tendermint34Client | undefined; - readonly queryClient: (QueryClient & AuthExtension & BankExtension & WasmExtension) | undefined; + readonly queryClient: + | (QueryClient & AuthExtension & BankExtension & TxExtension & WasmExtension) + | undefined; } export class CosmWasmClient { private readonly tmClient: Tendermint34Client | undefined; - private readonly queryClient: (QueryClient & AuthExtension & BankExtension & WasmExtension) | undefined; + private readonly queryClient: + | (QueryClient & AuthExtension & BankExtension & TxExtension & WasmExtension) + | undefined; private readonly codesCache = new Map(); private chainId: string | undefined; @@ -97,6 +103,7 @@ export class CosmWasmClient { setupAuthExtension, setupBankExtension, setupWasmExtension, + setupTxExtension, ); } } @@ -114,11 +121,13 @@ export class CosmWasmClient { return this.tmClient; } - protected getQueryClient(): (QueryClient & AuthExtension & BankExtension & WasmExtension) | undefined { + protected getQueryClient(): + | (QueryClient & AuthExtension & BankExtension & TxExtension & WasmExtension) + | undefined { return this.queryClient; } - protected forceGetQueryClient(): QueryClient & AuthExtension & BankExtension & WasmExtension { + protected forceGetQueryClient(): QueryClient & AuthExtension & BankExtension & TxExtension & WasmExtension { if (!this.queryClient) { throw new Error("Query client not available. You cannot use online functionality in offline mode."); } diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index 0bca4ead..7310b3c5 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Secp256k1HdWallet } from "@cosmjs/amino"; import { sha256 } from "@cosmjs/crypto"; -import { toHex } from "@cosmjs/encoding"; +import { toHex, toUtf8 } from "@cosmjs/encoding"; import { decodeTxRaw, DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; import { AminoMsgDelegate, @@ -17,12 +17,12 @@ import { DeepPartial, MsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx"; import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; import { MsgDelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx"; import { AuthInfo, TxBody, TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; -import { MsgStoreCode } from "cosmjs-types/cosmwasm/wasm/v1/tx"; +import { MsgExecuteContract, MsgStoreCode } from "cosmjs-types/cosmwasm/wasm/v1/tx"; import Long from "long"; import pako from "pako"; import protobuf from "protobufjs/minimal"; -import { MsgStoreCodeEncodeObject } from "./encodeobjects"; +import { MsgExecuteContractEncodeObject, MsgStoreCodeEncodeObject } from "./encodeobjects"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import { alice, @@ -71,6 +71,30 @@ describe("SigningCosmWasmClient", () => { }); }); + describe("simulate", () => { + it("works", async () => { + pendingWithoutWasmd(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix }); + const options = { ...defaultSigningClientOptions, prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); + + const executeContractMsg: MsgExecuteContractEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: alice.address0, + contract: deployedHackatom.instances[0].address, + msg: toUtf8(`{"release":{}}`), + funds: [], + }), + }; + const memo = "Go go go"; + const gasUsed = await client.simulate(alice.address0, [executeContractMsg], memo); + expect(gasUsed).toBeGreaterThanOrEqual(101_000); + expect(gasUsed).toBeLessThanOrEqual(106_000); + client.disconnect(); + }); + }); + describe("upload", () => { it("works", async () => { pendingWithoutWasmd(); diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index 76175a08..b7a007fc 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -29,7 +29,7 @@ import { StdFee, } from "@cosmjs/stargate"; import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; -import { assert } from "@cosmjs/utils"; +import { assert, assertDefined } from "@cosmjs/utils"; import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; import { MsgDelegate, MsgUndelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx"; import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; @@ -196,6 +196,25 @@ export class SigningCosmWasmClient extends CosmWasmClient { this.broadcastPollIntervalMs = options.broadcastPollIntervalMs; } + public async simulate( + signerAddress: string, + messages: readonly EncodeObject[], + memo: string | undefined, + ): Promise { + const anyMsgs = messages.map((m) => this.registry.encodeAsAny(m)); + const accountFromSigner = (await this.signer.getAccounts()).find( + (account) => account.address === signerAddress, + ); + if (!accountFromSigner) { + throw new Error("Failed to retrieve account from signer"); + } + const pubkey = encodeSecp256k1Pubkey(accountFromSigner.pubkey); + const { sequence } = await this.getSequence(signerAddress); + const { gasInfo } = await this.forceGetQueryClient().tx.simulate(anyMsgs, memo, pubkey, sequence); + assertDefined(gasInfo); + return Uint53.fromString(gasInfo.gasUsed.toString()).toNumber(); + } + /** Uploads code and returns a receipt, including the code ID */ public async upload( senderAddress: string,