diff --git a/packages/bcp/src/cosmwasmcodec.ts b/packages/bcp/src/cosmwasmcodec.ts index 2ca6e273..caf46ad7 100644 --- a/packages/bcp/src/cosmwasmcodec.ts +++ b/packages/bcp/src/cosmwasmcodec.ts @@ -56,12 +56,12 @@ export class CosmWasmCodec implements TxCodec { const built = buildUnsignedTx(unsigned, this.tokens); const signMsg = sortJson({ - account_number: nonceToAccountNumber(nonce), + account_number: nonceToAccountNumber(nonce).toString(), chain_id: Caip5.decode(unsigned.chainId), fee: (built.value as any).fee, memo: memo, msgs: (built.value as any).msg, - sequence: nonceToSequence(nonce), + sequence: nonceToSequence(nonce).toString(), }); const signBytes = toUtf8(JSON.stringify(signMsg)); diff --git a/packages/bcp/src/cosmwasmconnection.spec.ts b/packages/bcp/src/cosmwasmconnection.spec.ts index 100ddc3b..c59759a7 100644 --- a/packages/bcp/src/cosmwasmconnection.spec.ts +++ b/packages/bcp/src/cosmwasmconnection.spec.ts @@ -242,7 +242,7 @@ describe("CosmWasmConnection", () => { expect(signatures.length).toEqual(1); // TODO: the nonce we recover in response doesn't have accountNumber, only sequence - const signedSequence = parseInt(nonceToSequence(signed.signatures[0].nonce), 10); + const signedSequence = nonceToSequence(signed.signatures[0].nonce); expect(signatures[0].nonce).toEqual(signedSequence); expect(signatures[0].pubkey.algo).toEqual(signed.signatures[0].pubkey.algo); expect(toHex(signatures[0].pubkey.data)).toEqual( diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index 84177c3a..21b65fe4 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -309,7 +309,7 @@ export class CosmWasmConnection implements BlockchainConnection { const accountForHeight = await this.restClient.authAccounts(sender, response.height); // this is technically not the proper nonce. maybe this causes issues for sig validation? // leaving for now unless it causes issues - const sequence = (parseInt(accountForHeight.result.value.sequence, 10) - 1) as Nonce; + const sequence = (accountForHeight.result.value.sequence - 1) as Nonce; return parseTxsResponse(chainId, parseInt(response.height, 10), sequence, response, this.tokenInfo); } } diff --git a/packages/bcp/src/types.spec.ts b/packages/bcp/src/types.spec.ts index 0023e63f..3fab0b47 100644 --- a/packages/bcp/src/types.spec.ts +++ b/packages/bcp/src/types.spec.ts @@ -4,24 +4,24 @@ import { accountToNonce, nonceToAccountNumber, nonceToSequence } from "./types"; describe("nonceEncoding", () => { it("works for input in range", () => { const nonce = accountToNonce({ - account_number: "1234", - sequence: "7890", + account_number: 1234, + sequence: 7890, }); - expect(nonceToAccountNumber(nonce)).toEqual("1234"); - expect(nonceToSequence(nonce)).toEqual("7890"); + expect(nonceToAccountNumber(nonce)).toEqual(1234); + expect(nonceToSequence(nonce)).toEqual(7890); }); it("errors on input too large", () => { expect(() => accountToNonce({ - account_number: "1234567890", - sequence: "7890", + account_number: 1234567890, + sequence: 7890, }), ).toThrow(); expect(() => accountToNonce({ - account_number: "178", - sequence: "97320247923", + account_number: 178, + sequence: 97320247923, }), ).toThrow(); }); diff --git a/packages/bcp/src/types.ts b/packages/bcp/src/types.ts index 32133828..2f1ee35a 100644 --- a/packages/bcp/src/types.ts +++ b/packages/bcp/src/types.ts @@ -1,3 +1,4 @@ +import { types } from "@cosmwasm/sdk"; import { Nonce } from "@iov/bcp"; export interface TokenInfo { @@ -24,42 +25,35 @@ const maxSeq = 1 << 20; // NonceInfo is the data we need from account to create a nonce // Use this so no confusion about order of arguments -export interface NonceInfo { - readonly account_number: string; - readonly sequence: string; -} +export type NonceInfo = Pick; // this (lossily) encodes the two pieces of info (uint64) needed to sign into // one (53-bit) number. Cross your fingers. -/* eslint-disable-next-line @typescript-eslint/camelcase */ -export function accountToNonce({ account_number, sequence }: NonceInfo): Nonce { - const acct = parseInt(account_number, 10); - const seq = parseInt(sequence, 10); - +export function accountToNonce({ account_number: account, sequence }: NonceInfo): Nonce { // we allow 23 bits (8 million) for accounts, and 20 bits (1 million) for tx/account // let's fix this soon - if (acct > maxAcct) { + if (account > maxAcct) { throw new Error("Account number is greater than 2^23, must update Nonce handler"); } - if (seq > maxSeq) { + if (sequence > maxSeq) { throw new Error("Sequence is greater than 2^20, must update Nonce handler"); } - const val = acct * maxSeq + seq; + const val = account * maxSeq + sequence; return val as Nonce; } // this extracts info from nonce for signing -export function nonceToAccountNumber(nonce: Nonce): string { +export function nonceToAccountNumber(nonce: Nonce): number { const acct = nonce / maxSeq; if (acct > maxAcct) { throw new Error("Invalid Nonce, account number is higher than can safely be encoded in Nonce"); } - return Math.round(acct).toString(); + return Math.round(acct); } // this extracts info from nonce for signing -export function nonceToSequence(nonce: Nonce): string { +export function nonceToSequence(nonce: Nonce): number { const seq = nonce % maxSeq; - return Math.round(seq).toString(); + return Math.round(seq); } diff --git a/packages/bcp/types/types.d.ts b/packages/bcp/types/types.d.ts index e8ac6ce2..d89725f4 100644 --- a/packages/bcp/types/types.d.ts +++ b/packages/bcp/types/types.d.ts @@ -1,3 +1,4 @@ +import { types } from "@cosmwasm/sdk"; import { Nonce } from "@iov/bcp"; export interface TokenInfo { readonly denom: string; @@ -14,10 +15,7 @@ export interface TokenInfo { readonly fractionalDigits: number; } export declare type TokenInfos = ReadonlyArray; -export interface NonceInfo { - readonly account_number: string; - readonly sequence: string; -} -export declare function accountToNonce({ account_number, sequence }: NonceInfo): Nonce; -export declare function nonceToAccountNumber(nonce: Nonce): string; -export declare function nonceToSequence(nonce: Nonce): string; +export declare type NonceInfo = Pick; +export declare function accountToNonce({ account_number: account, sequence }: NonceInfo): Nonce; +export declare function nonceToAccountNumber(nonce: Nonce): number; +export declare function nonceToSequence(nonce: Nonce): number; diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index e44812ee..33f27ffd 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -1,10 +1,54 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { Encoding } from "@iov/encoding"; + import { RestClient } from "./restclient"; +import data from "./testdata/cosmoshub.json"; +import { StdTx } from "./types"; + +const { fromBase64 } = Encoding; const httpUrl = "http://localhost:1317"; +const defaultNetworkId = "testing"; +const faucetAddress = "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6"; + +function pendingWithoutCosmos(): void { + if (!process.env.COSMOS_ENABLED) { + return pending("Set COSMOS_ENABLED to enable Cosmos node-based tests"); + } +} describe("RestClient", () => { it("can be constructed", () => { const client = new RestClient(httpUrl); expect(client).toBeTruthy(); }); + + describe("nodeInfo", () => { + it("works", async () => { + pendingWithoutCosmos(); + const client = new RestClient(httpUrl); + const info = await client.nodeInfo(); + expect(info.node_info.network).toEqual(defaultNetworkId); + }); + }); + + describe("authAccounts", () => { + it("works", async () => { + pendingWithoutCosmos(); + const client = new RestClient(httpUrl); + const { result } = await client.authAccounts(faucetAddress); + const account = result.value; + expect(account.account_number).toEqual(4); + expect(account.sequence).toBeGreaterThanOrEqual(0); + }); + }); + + describe("encodeTx", () => { + it("works for cosmoshub example", async () => { + pendingWithoutCosmos(); + const tx: StdTx = data.tx.value; + const client = new RestClient(httpUrl); + expect(await client.encodeTx(tx)).toEqual(fromBase64(data.tx_data)); + }); + }); }); diff --git a/packages/sdk/src/restclient.ts b/packages/sdk/src/restclient.ts index c7d17e94..b210c873 100644 --- a/packages/sdk/src/restclient.ts +++ b/packages/sdk/src/restclient.ts @@ -139,7 +139,7 @@ export class RestClient { return responseData as BlocksResponse; } - // encodeTx returns the amino-encoding of the transaction + /** returns the amino-encoding of the transaction performed by the server */ public async encodeTx(stdTx: StdTx): Promise { const tx = { type: "cosmos-sdk/StdTx", value: stdTx }; const responseData = await this.post("/txs/encode", tx); diff --git a/packages/sdk/src/testdata/cosmoshub.json b/packages/sdk/src/testdata/cosmoshub.json new file mode 100644 index 00000000..cb33539c --- /dev/null +++ b/packages/sdk/src/testdata/cosmoshub.json @@ -0,0 +1,44 @@ +{ + "//source": "https://hubble.figment.network/cosmos/chains/cosmoshub-3/blocks/415777/transactions/2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4?format=json", + "tx": { + "type": "cosmos-sdk/StdTx", + "value": { + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "from_address": "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq", + "to_address": "cosmos1nynns8ex9fq6sjjfj8k79ymkdz4sqth06xexae", + "amount": [ + { + "denom": "uatom", + "amount": "35997500" + } + ] + } + } + ], + "fee": { + "amount": [ + { + "denom": "uatom", + "amount": "2500" + } + ], + "gas": "100000" + }, + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAq" + }, + "signature": "NK1Oy4EUGAsoC03c1wi9GG03JC/39LEdautC5Jk643oIbEPqeXHMwaqbdvO/Jws0X/NAXaN8SAy2KNY5Qml+5Q==" + } + ], + "memo": "" + } + }, + "tx_data": "ygEoKBapCkOoo2GaChRZgJnSW8Lg8zwesNppHWhJTrk8uhIUmSc4HyYqQahKSZHt4pN2aKsALu8aEQoFdWF0b20SCDM1OTk3NTAwEhMKDQoFdWF0b20SBDI1MDAQoI0GGmoKJuta6YchA5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAqEkA0rU7LgRQYCygLTdzXCL0YbTckL/f0sR1q60LkmTrjeghsQ+p5cczBqpt2878nCzRf80Bdo3xIDLYo1jlCaX7l", + "id": "2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4" +} diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 1d5e9c86..9ce32263 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -64,6 +64,6 @@ export interface BaseAccount { readonly address: string; readonly coins: ReadonlyArray; readonly public_key: AccountPubKey; - readonly account_number: string; - readonly sequence: string; + readonly account_number: number; + readonly sequence: number; } diff --git a/packages/sdk/types/restclient.d.ts b/packages/sdk/types/restclient.d.ts index b818be1e..08f16827 100644 --- a/packages/sdk/types/restclient.d.ts +++ b/packages/sdk/types/restclient.d.ts @@ -71,6 +71,7 @@ export declare class RestClient { nodeInfo(): Promise; blocksLatest(): Promise; blocks(height: number): Promise; + /** returns the amino-encoding of the transaction performed by the server */ encodeTx(stdTx: StdTx): Promise; authAccounts(address: string, height?: string): Promise; txs(query: string): Promise; diff --git a/packages/sdk/types/types.d.ts b/packages/sdk/types/types.d.ts index 4f1bb326..a2c0abd1 100644 --- a/packages/sdk/types/types.d.ts +++ b/packages/sdk/types/types.d.ts @@ -45,6 +45,6 @@ export interface BaseAccount { readonly address: string; readonly coins: ReadonlyArray; readonly public_key: AccountPubKey; - readonly account_number: string; - readonly sequence: string; + readonly account_number: number; + readonly sequence: number; }