From 0a10fa57ac0352e617c02adf739fb915646d39a6 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 17 Feb 2020 12:55:41 +0100 Subject: [PATCH 1/7] Make fee payments configurable --- packages/sdk/src/cosmwasmclient.ts | 79 ++++++++++++++------------ packages/sdk/types/cosmwasmclient.d.ts | 18 +++++- 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index be66846d..e611eb04 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -15,34 +15,34 @@ import { StdSignature, } from "./types"; -const defaultUploadFee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "1000000", // one million -}; +export interface FeeTable { + readonly upload: StdFee; + readonly init: StdFee; + readonly exec: StdFee; + readonly send: StdFee; +} -const defaultInitFee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "500000", // 500k -}; +const feeAmount = (amount: number, denom: string): readonly Coin[] => [ + { amount: amount.toString(), denom: denom }, +]; -const defaultExecFee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "200000", // 200k +const defaultFeeTable: FeeTable = { + upload: { + amount: feeAmount(25000, "ucosm"), + gas: "1000000", // one million + }, + init: { + amount: feeAmount(12500, "ucosm"), + gas: "500000", // 500k + }, + exec: { + amount: feeAmount(5000, "ucosm"), + gas: "200000", // 200k + }, + send: { + amount: feeAmount(2000, "ucosm"), + gas: "80000", // 80k + }, }; export interface SigningCallback { @@ -97,23 +97,29 @@ export interface ExecuteResult { } export class CosmWasmClient { - public static makeReadOnly(url: string): CosmWasmClient { - return new CosmWasmClient(url); + public static makeReadOnly(url: string, feeTable?: Partial): CosmWasmClient { + return new CosmWasmClient(url, undefined, feeTable); } public static makeWritable( url: string, senderAddress: string, signCallback: SigningCallback, + feeTable?: Partial, ): CosmWasmClient { - return new CosmWasmClient(url, { - senderAddress: senderAddress, - signCallback: signCallback, - }); + return new CosmWasmClient( + url, + { + senderAddress: senderAddress, + signCallback: signCallback, + }, + feeTable, + ); } private readonly restClient: RestClient; private readonly signingData: SigningData | undefined; + private readonly feeTable: FeeTable; private get senderAddress(): string { if (!this.signingData) throw new Error("Signing data not set in this client"); @@ -125,9 +131,10 @@ export class CosmWasmClient { return this.signingData.signCallback; } - private constructor(url: string, signingData?: SigningData) { + private constructor(url: string, signingData?: SigningData, feeTable?: Partial) { this.restClient = new RestClient(url); this.signingData = signingData; + this.feeTable = { ...defaultFeeTable, ...(feeTable || {}) }; } public async chainId(): Promise { @@ -234,7 +241,7 @@ export class CosmWasmClient { builder: "", }, }; - const fee = defaultUploadFee; + const fee = this.feeTable.upload; const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.chainId(); const signBytes = makeSignBytes([storeCodeMsg], fee, chainId, memo, accountNumber, sequence); @@ -270,7 +277,7 @@ export class CosmWasmClient { init_funds: transferAmount || [], }, }; - const fee = defaultInitFee; + const fee = this.feeTable.init; const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.chainId(); const signBytes = makeSignBytes([instantiateMsg], fee, chainId, memo, accountNumber, sequence); @@ -304,7 +311,7 @@ export class CosmWasmClient { sent_funds: transferAmount || [], }, }; - const fee = defaultExecFee; + const fee = this.feeTable.exec; const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.chainId(); const signBytes = makeSignBytes([executeMsg], fee, chainId, memo, accountNumber, sequence); diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index 93fd5216..d4464a80 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -1,6 +1,12 @@ import { Log } from "./logs"; import { BlockResponse, TxsResponse } from "./restclient"; -import { Coin, CosmosSdkAccount, CosmosSdkTx, StdSignature } from "./types"; +import { Coin, CosmosSdkAccount, CosmosSdkTx, StdFee, StdSignature } from "./types"; +export interface FeeTable { + readonly upload: StdFee; + readonly init: StdFee; + readonly exec: StdFee; + readonly send: StdFee; +} export interface SigningCallback { (signBytes: Uint8Array): Promise; } @@ -28,10 +34,16 @@ export interface ExecuteResult { readonly logs: readonly Log[]; } export declare class CosmWasmClient { - static makeReadOnly(url: string): CosmWasmClient; - static makeWritable(url: string, senderAddress: string, signCallback: SigningCallback): CosmWasmClient; + static makeReadOnly(url: string, feeTable?: Partial): CosmWasmClient; + static makeWritable( + url: string, + senderAddress: string, + signCallback: SigningCallback, + feeTable?: Partial, + ): CosmWasmClient; private readonly restClient; private readonly signingData; + private readonly feeTable; private get senderAddress(); private get signCallback(); private constructor(); From 6d3865a27e048e56553a37b14e03e7b4cce0edee Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Sun, 16 Feb 2020 19:55:07 +0100 Subject: [PATCH 2/7] Add sendToken() and test --- packages/sdk/src/cosmwasmclient.spec.ts | 31 ++++++++++++++++++++++ packages/sdk/src/cosmwasmclient.ts | 34 +++++++++++++++++++++++++ packages/sdk/types/cosmwasmclient.d.ts | 1 + 3 files changed, 66 insertions(+) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 319610a8..4742790f 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -446,6 +446,37 @@ describe("CosmWasmClient", () => { }); }); + describe("sendToken", () => { + it("works", async () => { + pendingWithoutCosmos(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); + + // instantiate + const transferAmount: readonly Coin[] = [ + { + amount: "7890", + denom: "ucosm", + }, + ]; + const beneficiaryAddress = makeRandomAddress(); + + // no tokens here + const before = await client.getAccount(beneficiaryAddress); + expect(before).toBeUndefined(); + + // send + const result = await client.sendToken(beneficiaryAddress, transferAmount, "for dinner"); + const [firstLog] = result.logs; + expect(firstLog).toBeTruthy(); + + // got tokens + const after = await client.getAccount(beneficiaryAddress); + assert(after); + expect(after.coins).toEqual(transferAmount); + }); + }); + describe("queryContractRaw", () => { const configKey = toAscii("config"); const otherKey = toAscii("this_does_not_exist"); diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index e611eb04..b7dd7c0f 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -10,6 +10,7 @@ import { CosmosSdkTx, MsgExecuteContract, MsgInstantiateContract, + MsgSend, MsgStoreCode, StdFee, StdSignature, @@ -329,6 +330,39 @@ export class CosmWasmClient { }; } + public async sendToken( + recipientAddress: string, + transferAmount: readonly Coin[], + memo = "", + ): Promise { + const sendMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + // eslint-disable-next-line @typescript-eslint/camelcase + from_address: this.senderAddress, + // eslint-disable-next-line @typescript-eslint/camelcase + to_address: recipientAddress, + amount: transferAmount, + }, + }; + const fee = this.feeTable.send; + const { accountNumber, sequence } = await this.getNonce(); + const chainId = await this.chainId(); + const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await this.signCallback(signBytes); + const signedTx = { + msg: [sendMsg], + fee: fee, + memo: memo, + signatures: [signature], + }; + + const result = await this.postTx(marshalTx(signedTx)); + return { + logs: result.logs, + }; + } + /** * Returns the data at the key if present (raw contract dependent storage data) * or null if no data at this key. diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index d4464a80..f3fba702 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -83,6 +83,7 @@ export declare class CosmWasmClient { memo?: string, transferAmount?: readonly Coin[], ): Promise; + sendToken(recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise; /** * Returns the data at the key if present (raw contract dependent storage data) * or null if no data at this key. From 2d7c4e09a36b6d23fef8305b46dd09008ec8c9ab Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Sun, 16 Feb 2020 20:12:04 +0100 Subject: [PATCH 3/7] Clean up test cases --- packages/sdk/src/cosmwasmclient.spec.ts | 47 ++++++------------------- packages/sdk/src/cosmwasmclient.ts | 7 ++-- packages/sdk/types/cosmwasmclient.d.ts | 2 +- 3 files changed, 13 insertions(+), 43 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 4742790f..32f9ea5e 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -222,50 +222,23 @@ describe("CosmWasmClient", () => { beforeAll(async () => { if (cosmosEnabled()) { const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = CosmWasmClient.makeReadOnly(httpUrl); + const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); const memo = "My first contract on chain"; - const sendMsg: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: faucet.address, - to_address: makeRandomAddress(), - amount: [ - { - denom: "ucosm", - amount: "1234567", - }, - ], + const recipient = makeRandomAddress(); + const transferAmount = [ + { + denom: "ucosm", + amount: "1234567", }, - }; + ]; + const result = await client.sendToken(recipient, transferAmount, memo); - const fee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "890000", - }; - - const chainId = await client.chainId(); - const { accountNumber, sequence } = await client.getNonce(faucet.address); - const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await pen.sign(signBytes); - const signedTx = { - msg: [sendMsg], - fee: fee, - memo: memo, - signatures: [signature], - }; - - const result = await client.postTx(marshalTx(signedTx)); await sleep(50); // wait until tx is indexed const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); posted = { - sender: sendMsg.value.from_address, - recipient: sendMsg.value.to_address, + sender: faucet.address, + recipient: recipient, hash: result.transactionHash, height: Number.parseInt(txDetails.height, 10), tx: txDetails.tx, diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index b7dd7c0f..b74be3f0 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -334,7 +334,7 @@ export class CosmWasmClient { recipientAddress: string, transferAmount: readonly Coin[], memo = "", - ): Promise { + ): Promise { const sendMsg: MsgSend = { type: "cosmos-sdk/MsgSend", value: { @@ -357,10 +357,7 @@ export class CosmWasmClient { signatures: [signature], }; - const result = await this.postTx(marshalTx(signedTx)); - return { - logs: result.logs, - }; + return this.postTx(marshalTx(signedTx)); } /** diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index f3fba702..43a72f4b 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -83,7 +83,7 @@ export declare class CosmWasmClient { memo?: string, transferAmount?: readonly Coin[], ): Promise; - sendToken(recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise; + sendToken(recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise; /** * Returns the data at the key if present (raw contract dependent storage data) * or null if no data at this key. From 52d9e0b5c3478e9a2d190eff8df2ef53982a4491 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 17 Feb 2020 12:57:33 +0100 Subject: [PATCH 4/7] Convert function to function --- packages/sdk/src/cosmwasmclient.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index b74be3f0..1a31b4bf 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -23,25 +23,25 @@ export interface FeeTable { readonly send: StdFee; } -const feeAmount = (amount: number, denom: string): readonly Coin[] => [ - { amount: amount.toString(), denom: denom }, -]; +function singleAmount(amount: number, denom: string): readonly Coin[] { + return [{ amount: amount.toString(), denom: denom }]; +} const defaultFeeTable: FeeTable = { upload: { - amount: feeAmount(25000, "ucosm"), + amount: singleAmount(25000, "ucosm"), gas: "1000000", // one million }, init: { - amount: feeAmount(12500, "ucosm"), + amount: singleAmount(12500, "ucosm"), gas: "500000", // 500k }, exec: { - amount: feeAmount(5000, "ucosm"), + amount: singleAmount(5000, "ucosm"), gas: "200000", // 200k }, send: { - amount: feeAmount(2000, "ucosm"), + amount: singleAmount(2000, "ucosm"), gas: "80000", // 80k }, }; From 0010c1bc77911f545dd204d9ecf382a23ee72323 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 17 Feb 2020 13:00:38 +0100 Subject: [PATCH 5/7] Remove fees argument from makeReadOnly --- packages/sdk/src/cosmwasmclient.ts | 22 +++++++++++----------- packages/sdk/types/cosmwasmclient.d.ts | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index 1a31b4bf..0ce088dc 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -27,7 +27,7 @@ function singleAmount(amount: number, denom: string): readonly Coin[] { return [{ amount: amount.toString(), denom: denom }]; } -const defaultFeeTable: FeeTable = { +const defaultFees: FeeTable = { upload: { amount: singleAmount(25000, "ucosm"), gas: "1000000", // one million @@ -98,8 +98,8 @@ export interface ExecuteResult { } export class CosmWasmClient { - public static makeReadOnly(url: string, feeTable?: Partial): CosmWasmClient { - return new CosmWasmClient(url, undefined, feeTable); + public static makeReadOnly(url: string): CosmWasmClient { + return new CosmWasmClient(url, undefined, {}); } public static makeWritable( @@ -114,13 +114,13 @@ export class CosmWasmClient { senderAddress: senderAddress, signCallback: signCallback, }, - feeTable, + feeTable || {}, ); } private readonly restClient: RestClient; private readonly signingData: SigningData | undefined; - private readonly feeTable: FeeTable; + private readonly fees: FeeTable; private get senderAddress(): string { if (!this.signingData) throw new Error("Signing data not set in this client"); @@ -132,10 +132,10 @@ export class CosmWasmClient { return this.signingData.signCallback; } - private constructor(url: string, signingData?: SigningData, feeTable?: Partial) { + private constructor(url: string, signingData: SigningData | undefined, customFees: Partial) { this.restClient = new RestClient(url); this.signingData = signingData; - this.feeTable = { ...defaultFeeTable, ...(feeTable || {}) }; + this.fees = { ...defaultFees, ...customFees }; } public async chainId(): Promise { @@ -242,7 +242,7 @@ export class CosmWasmClient { builder: "", }, }; - const fee = this.feeTable.upload; + const fee = this.fees.upload; const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.chainId(); const signBytes = makeSignBytes([storeCodeMsg], fee, chainId, memo, accountNumber, sequence); @@ -278,7 +278,7 @@ export class CosmWasmClient { init_funds: transferAmount || [], }, }; - const fee = this.feeTable.init; + const fee = this.fees.init; const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.chainId(); const signBytes = makeSignBytes([instantiateMsg], fee, chainId, memo, accountNumber, sequence); @@ -312,7 +312,7 @@ export class CosmWasmClient { sent_funds: transferAmount || [], }, }; - const fee = this.feeTable.exec; + const fee = this.fees.exec; const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.chainId(); const signBytes = makeSignBytes([executeMsg], fee, chainId, memo, accountNumber, sequence); @@ -345,7 +345,7 @@ export class CosmWasmClient { amount: transferAmount, }, }; - const fee = this.feeTable.send; + const fee = this.fees.send; const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.chainId(); const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index 43a72f4b..49e49d7a 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -34,7 +34,7 @@ export interface ExecuteResult { readonly logs: readonly Log[]; } export declare class CosmWasmClient { - static makeReadOnly(url: string, feeTable?: Partial): CosmWasmClient; + static makeReadOnly(url: string): CosmWasmClient; static makeWritable( url: string, senderAddress: string, @@ -43,7 +43,7 @@ export declare class CosmWasmClient { ): CosmWasmClient; private readonly restClient; private readonly signingData; - private readonly feeTable; + private readonly fees; private get senderAddress(); private get signCallback(); private constructor(); From 4a3be36bc6d47c4a2b2fbd2eab138b3e851280f2 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 17 Feb 2020 13:07:34 +0100 Subject: [PATCH 6/7] Rename to CosmWasmClient.sendTokens --- packages/sdk/src/cosmwasmclient.spec.ts | 6 +++--- packages/sdk/src/cosmwasmclient.ts | 2 +- packages/sdk/types/cosmwasmclient.d.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 32f9ea5e..3bfe3571 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -232,7 +232,7 @@ describe("CosmWasmClient", () => { amount: "1234567", }, ]; - const result = await client.sendToken(recipient, transferAmount, memo); + const result = await client.sendTokens(recipient, transferAmount, memo); await sleep(50); // wait until tx is indexed const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); @@ -419,7 +419,7 @@ describe("CosmWasmClient", () => { }); }); - describe("sendToken", () => { + describe("sendTokens", () => { it("works", async () => { pendingWithoutCosmos(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); @@ -439,7 +439,7 @@ describe("CosmWasmClient", () => { expect(before).toBeUndefined(); // send - const result = await client.sendToken(beneficiaryAddress, transferAmount, "for dinner"); + const result = await client.sendTokens(beneficiaryAddress, transferAmount, "for dinner"); const [firstLog] = result.logs; expect(firstLog).toBeTruthy(); diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index 0ce088dc..18b5319d 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -330,7 +330,7 @@ export class CosmWasmClient { }; } - public async sendToken( + public async sendTokens( recipientAddress: string, transferAmount: readonly Coin[], memo = "", diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index 49e49d7a..2a5208bb 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -83,7 +83,7 @@ export declare class CosmWasmClient { memo?: string, transferAmount?: readonly Coin[], ): Promise; - sendToken(recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise; + sendTokens(recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise; /** * Returns the data at the key if present (raw contract dependent storage data) * or null if no data at this key. From 5860d803899260db6a2c6631a5e93e7e3218a2b5 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 17 Feb 2020 13:11:25 +0100 Subject: [PATCH 7/7] Remove unnecessary memo --- packages/sdk/src/cosmwasmclient.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 3bfe3571..0c8292b3 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -224,7 +224,6 @@ describe("CosmWasmClient", () => { const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); - const memo = "My first contract on chain"; const recipient = makeRandomAddress(); const transferAmount = [ { @@ -232,7 +231,7 @@ describe("CosmWasmClient", () => { amount: "1234567", }, ]; - const result = await client.sendTokens(recipient, transferAmount, memo); + const result = await client.sendTokens(recipient, transferAmount); await sleep(50); // wait until tx is indexed const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash);