From 86467822f9bb78af2b832b0ab38f684bd7887687 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 19 Nov 2021 00:11:10 +0100 Subject: [PATCH] Add "auto" option to fee --- packages/cli/examples/simulate.ts | 38 ++++++++++++++++--- .../src/signingcosmwasmclient.spec.ts | 27 +++++++++++++ .../src/signingcosmwasmclient.ts | 37 ++++++++++++------ .../src/signingstargateclient.spec.ts | 22 +++++++++++ .../stargate/src/signingstargateclient.ts | 26 +++++++++---- 5 files changed, 126 insertions(+), 24 deletions(-) diff --git a/packages/cli/examples/simulate.ts b/packages/cli/examples/simulate.ts index d027a713..3bec3df2 100644 --- a/packages/cli/examples/simulate.ts +++ b/packages/cli/examples/simulate.ts @@ -22,9 +22,19 @@ const rpcEndpoint = "ws://localhost:26658"; const gasPrice = GasPrice.fromString("0.025ucosm"); // Setup client -const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet); +const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet, { gasPrice: gasPrice }); -// Send transaction (using sendTokens) +// Send transaction (using sendTokens with auto gas) +{ + const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; + const amount = coins(1234567, "ucosm"); + const memo = "With simulate"; + const result = await client.sendTokens(account.address, recipient, amount, "auto", memo); + assertIsBroadcastTxSuccess(result); + console.log("Successfully broadcasted:", result); +} + +// Send transaction (using sendTokens with manual gas) { const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; const amount = coins(1234567, "ucosm"); @@ -38,13 +48,31 @@ const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet }; const memo = "With simulate"; const gasEstimation = await client.simulate(account.address, [sendMsg], memo); - const fee = calculateFee(Math.floor(gasEstimation * 1.3), gasPrice); + const fee = calculateFee(Math.round(gasEstimation * 1.3), gasPrice); const result = await client.sendTokens(account.address, recipient, amount, fee, memo); assertIsBroadcastTxSuccess(result); console.log("Successfully broadcasted:", result); } -// Send transaction (using signAndBroadcast) +// Send transaction (using signAndBroadcast with auto gas) +{ + const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; + const amount = coins(1234567, "ucosm"); + const sendMsg: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress: account.address, + toAddress: recipient, + amount: amount, + }, + }; + const memo = "With simulate"; + const result = await client.signAndBroadcast(account.address, [sendMsg], "auto", memo); + assertIsBroadcastTxSuccess(result); + console.log("Successfully broadcasted:", result); +} + +// Send transaction (using signAndBroadcast with manual gas) { const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; const amount = coins(1234567, "ucosm"); @@ -58,7 +86,7 @@ const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet }; const memo = "With simulate"; const gasEstimation = await client.simulate(account.address, [sendMsg], memo); - const fee = calculateFee(Math.floor(gasEstimation * 1.3), gasPrice); + const fee = calculateFee(Math.round(gasEstimation * 1.3), gasPrice); const result = await client.signAndBroadcast(account.address, [sendMsg], fee, memo); assertIsBroadcastTxSuccess(result); console.log("Successfully broadcasted:", result); diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index f6fd0dd6..02a40a74 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -28,6 +28,7 @@ import { alice, defaultClearAdminFee, defaultExecuteFee, + defaultGasPrice, defaultInstantiateFee, defaultMigrateFee, defaultSendFee, @@ -600,6 +601,32 @@ describe("SigningCosmWasmClient", () => { client.disconnect(); }); + it("works with auto gas", async () => { + pendingWithoutWasmd(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix }); + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, { + ...defaultSigningClientOptions, + prefix: wasmd.prefix, + gasPrice: defaultGasPrice, + }); + const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; + + const msg = MsgDelegate.fromPartial({ + delegatorAddress: alice.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "ustake"), + }); + const msgAny: MsgDelegateEncodeObject = { + typeUrl: msgDelegateTypeUrl, + value: msg, + }; + const memo = "Use your power wisely"; + const result = await client.signAndBroadcast(alice.address0, [msgAny], "auto", memo); + assertIsBroadcastTxSuccess(result); + + client.disconnect(); + }); + it("works with a modifying signer", async () => { pendingWithoutWasmd(); const wallet = await ModifyingDirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index b7a007fc..b8465523 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -17,8 +17,10 @@ import { AminoTypes, BroadcastTxFailure, BroadcastTxResponse, + calculateFee, Coin, defaultRegistryTypes, + GasPrice, isBroadcastTxFailure, logs, MsgDelegateEncodeObject, @@ -144,6 +146,7 @@ export interface SigningCosmWasmClientOptions { readonly prefix?: string; readonly broadcastTimeoutMs?: number; readonly broadcastPollIntervalMs?: number; + readonly gasPrice?: GasPrice; } export class SigningCosmWasmClient extends CosmWasmClient { @@ -153,6 +156,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { private readonly signer: OfflineSigner; private readonly aminoTypes: AminoTypes; + private readonly gasPrice: GasPrice | undefined; public static async connectWithSigner( endpoint: string, @@ -194,6 +198,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { this.signer = signer; this.broadcastTimeoutMs = options.broadcastTimeoutMs; this.broadcastPollIntervalMs = options.broadcastPollIntervalMs; + this.gasPrice = options.gasPrice; } public async simulate( @@ -219,7 +224,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { public async upload( senderAddress: string, wasmCode: Uint8Array, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const compressed = pako.gzip(wasmCode, { level: 9 }); @@ -253,7 +258,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { codeId: number, msg: Record, label: string, - fee: StdFee, + fee: StdFee | "auto", options: InstantiateOptions = {}, ): Promise { const instantiateContractMsg: MsgInstantiateContractEncodeObject = { @@ -284,7 +289,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { senderAddress: string, contractAddress: string, newAdmin: string, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const updateAdminMsg: MsgUpdateAdminEncodeObject = { @@ -308,7 +313,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { public async clearAdmin( senderAddress: string, contractAddress: string, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const clearAdminMsg: MsgClearAdminEncodeObject = { @@ -333,7 +338,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { contractAddress: string, codeId: number, migrateMsg: Record, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const migrateContractMsg: MsgMigrateContractEncodeObject = { @@ -359,7 +364,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { senderAddress: string, contractAddress: string, msg: Record, - fee: StdFee, + fee: StdFee | "auto", memo = "", funds?: readonly Coin[], ): Promise { @@ -386,7 +391,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { senderAddress: string, recipientAddress: string, amount: readonly Coin[], - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const sendMsg: MsgSendEncodeObject = { @@ -404,7 +409,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { delegatorAddress: string, validatorAddress: string, amount: Coin, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const delegateMsg: MsgDelegateEncodeObject = { @@ -418,7 +423,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { delegatorAddress: string, validatorAddress: string, amount: Coin, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const undelegateMsg: MsgUndelegateEncodeObject = { @@ -431,7 +436,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { public async withdrawRewards( delegatorAddress: string, validatorAddress: string, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const withdrawDelegatorRewardMsg: MsgWithdrawDelegatorRewardEncodeObject = { @@ -452,10 +457,18 @@ export class SigningCosmWasmClient extends CosmWasmClient { public async signAndBroadcast( signerAddress: string, messages: readonly EncodeObject[], - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { - const txRaw = await this.sign(signerAddress, messages, fee, memo); + let usedFee: StdFee; + if (fee == "auto") { + assertDefined(this.gasPrice, "Gas price must be set in the client options when auto gas is used."); + const gasEstimation = await this.simulate(signerAddress, messages, memo); + usedFee = calculateFee(Math.round(gasEstimation * 1.3), this.gasPrice); + } else { + usedFee = fee; + } + const txRaw = await this.sign(signerAddress, messages, usedFee, memo); const txBytes = TxRaw.encode(txRaw).finish(); return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs); } diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index bcc2e138..dd25f997 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -16,6 +16,7 @@ import { MsgDelegateEncodeObject, MsgSendEncodeObject } from "./encodeobjects"; import { PrivateSigningStargateClient, SigningStargateClient } from "./signingstargateclient"; import { assertIsBroadcastTxSuccess, isBroadcastTxFailure } from "./stargateclient"; import { + defaultGasPrice, defaultSendFee, defaultSigningClientOptions, faucet, @@ -273,6 +274,27 @@ describe("SigningStargateClient", () => { assertIsBroadcastTxSuccess(result); }); + it("works with auto gas", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + gasPrice: defaultGasPrice, + }); + + const msg = MsgDelegate.fromPartial({ + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "ustake"), + }); + const msgAny: MsgDelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }; + const result = await client.signAndBroadcast(faucet.address0, [msgAny], "auto"); + assertIsBroadcastTxSuccess(result); + }); + it("works with a modifying signer", async () => { pendingWithoutSimapp(); const wallet = await ModifyingDirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index 120603f8..ec8d55af 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -68,6 +68,7 @@ import { MsgUndelegateEncodeObject, MsgWithdrawDelegatorRewardEncodeObject, } from "./encodeobjects"; +import { calculateFee, GasPrice } from "./fee"; import { BroadcastTxResponse, StargateClient } from "./stargateclient"; export const defaultRegistryTypes: ReadonlyArray<[string, GeneratedType]> = [ @@ -131,6 +132,7 @@ export interface SigningStargateClientOptions { readonly prefix?: string; readonly broadcastTimeoutMs?: number; readonly broadcastPollIntervalMs?: number; + readonly gasPrice?: GasPrice; } export class SigningStargateClient extends StargateClient { @@ -140,6 +142,7 @@ export class SigningStargateClient extends StargateClient { private readonly signer: OfflineSigner; private readonly aminoTypes: AminoTypes; + private readonly gasPrice: GasPrice | undefined; public static async connectWithSigner( endpoint: string, @@ -179,6 +182,7 @@ export class SigningStargateClient extends StargateClient { this.signer = signer; this.broadcastTimeoutMs = options.broadcastTimeoutMs; this.broadcastPollIntervalMs = options.broadcastPollIntervalMs; + this.gasPrice = options.gasPrice; } public async simulate( @@ -204,7 +208,7 @@ export class SigningStargateClient extends StargateClient { senderAddress: string, recipientAddress: string, amount: readonly Coin[], - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const sendMsg: MsgSendEncodeObject = { @@ -222,7 +226,7 @@ export class SigningStargateClient extends StargateClient { delegatorAddress: string, validatorAddress: string, amount: Coin, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const delegateMsg: MsgDelegateEncodeObject = { @@ -240,7 +244,7 @@ export class SigningStargateClient extends StargateClient { delegatorAddress: string, validatorAddress: string, amount: Coin, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const undelegateMsg: MsgUndelegateEncodeObject = { @@ -257,7 +261,7 @@ export class SigningStargateClient extends StargateClient { public async withdrawRewards( delegatorAddress: string, validatorAddress: string, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const withdrawMsg: MsgWithdrawDelegatorRewardEncodeObject = { @@ -279,7 +283,7 @@ export class SigningStargateClient extends StargateClient { timeoutHeight: Height | undefined, /** timeout in seconds */ timeoutTimestamp: number | undefined, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const timeoutTimestampNanoseconds = timeoutTimestamp @@ -303,10 +307,18 @@ export class SigningStargateClient extends StargateClient { public async signAndBroadcast( signerAddress: string, messages: readonly EncodeObject[], - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { - const txRaw = await this.sign(signerAddress, messages, fee, memo); + let usedFee: StdFee; + if (fee == "auto") { + assertDefined(this.gasPrice, "Gas price must be set in the client options when auto gas is used."); + const gasEstimation = await this.simulate(signerAddress, messages, memo); + usedFee = calculateFee(Math.round(gasEstimation * 1.3), this.gasPrice); + } else { + usedFee = fee; + } + const txRaw = await this.sign(signerAddress, messages, usedFee, memo); const txBytes = TxRaw.encode(txRaw).finish(); return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs); }