From 669905b11efcd877c8f11a43f7ca87311cdcaeda Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Sun, 9 Feb 2020 14:55:59 +0100 Subject: [PATCH] Make bank tokens explicit --- packages/bcp/src/cosmwasmcodec.ts | 8 ++--- packages/bcp/src/cosmwasmconnection.spec.ts | 34 +++++++++++---------- packages/bcp/src/cosmwasmconnection.ts | 19 +++++++----- packages/bcp/src/cosmwasmconnector.ts | 2 +- packages/bcp/src/decode.spec.ts | 4 +-- packages/bcp/src/decode.ts | 14 ++++----- packages/bcp/src/encode.spec.ts | 4 +-- packages/bcp/src/encode.ts | 12 ++++---- packages/bcp/src/types.ts | 4 +-- packages/bcp/types/cosmwasmconnection.d.ts | 15 +++++---- 10 files changed, 62 insertions(+), 54 deletions(-) diff --git a/packages/bcp/src/cosmwasmcodec.ts b/packages/bcp/src/cosmwasmcodec.ts index b370bdd1..2df8f0ef 100644 --- a/packages/bcp/src/cosmwasmcodec.ts +++ b/packages/bcp/src/cosmwasmcodec.ts @@ -26,13 +26,13 @@ import { pubkeyToAddress } from "./address"; import { Caip5 } from "./caip5"; import { parseTx } from "./decode"; import { buildSignedTx, buildUnsignedTx } from "./encode"; -import { nonceToAccountNumber, nonceToSequence, TokenInfos } from "./types"; +import { BankTokens, nonceToAccountNumber, nonceToSequence } from "./types"; export class CosmWasmCodec implements TxCodec { private readonly addressPrefix: CosmosAddressBech32Prefix; - private readonly tokens: TokenInfos; + private readonly tokens: BankTokens; - public constructor(addressPrefix: CosmosAddressBech32Prefix, tokens: TokenInfos) { + public constructor(addressPrefix: CosmosAddressBech32Prefix, tokens: BankTokens) { this.addressPrefix = addressPrefix; this.tokens = tokens; } @@ -93,7 +93,7 @@ export class CosmWasmCodec implements TxCodec { const defaultPrefix = "cosmos" as CosmosAddressBech32Prefix; -const defaultTokens: TokenInfos = [ +const defaultTokens: BankTokens = [ { fractionalDigits: 6, ticker: "ATOM", diff --git a/packages/bcp/src/cosmwasmconnection.spec.ts b/packages/bcp/src/cosmwasmconnection.spec.ts index 36e29773..cc3a5f57 100644 --- a/packages/bcp/src/cosmwasmconnection.spec.ts +++ b/packages/bcp/src/cosmwasmconnection.spec.ts @@ -46,20 +46,22 @@ describe("CosmWasmConnection", () => { const defaultPrefix = "cosmos" as CosmosAddressBech32Prefix; // this is for wasmd blockchain - const defaultTokens: TokenConfiguration = [ - { - fractionalDigits: 6, - name: "Fee Token", - ticker: "COSM", - denom: "ucosm", - }, - { - fractionalDigits: 6, - name: "Staking Token", - ticker: "STAKE", - denom: "ustake", - }, - ]; + const defaultTokens: TokenConfiguration = { + bank: [ + { + fractionalDigits: 6, + name: "Fee Token", + ticker: "COSM", + denom: "ucosm", + }, + { + fractionalDigits: 6, + name: "Staking Token", + ticker: "STAKE", + denom: "ustake", + }, + ], + }; describe("establish", () => { it("can connect to Cosmos via http", async () => { @@ -212,7 +214,7 @@ describe("CosmWasmConnection", () => { }); const nonce = await connection.getNonce({ address: faucetAddress }); // TODO: we need to use custom codecs everywhere - const codec = new CosmWasmCodec(defaultPrefix, defaultTokens); + const codec = new CosmWasmCodec(defaultPrefix, defaultTokens.bank); const signed = await profile.signTransaction(faucet, unsigned, codec, nonce); const postableBytes = codec.bytesToPost(signed); const response = await connection.postTx(postableBytes); @@ -277,7 +279,7 @@ describe("CosmWasmConnection", () => { }); const nonce = await connection.getNonce({ address: faucetAddress }); // TODO: we need to use custom codecs everywhere - const codec = new CosmWasmCodec(defaultPrefix, defaultTokens); + const codec = new CosmWasmCodec(defaultPrefix, defaultTokens.bank); const signed = await profile.signTransaction(faucet, unsigned, codec, nonce); const postableBytes = codec.bytesToPost(signed); const response = await connection.postTx(postableBytes); diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index 25790a6a..bdd10989 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -37,7 +37,7 @@ import { Stream } from "xstream"; import { decodeCosmosPubkey, pubkeyToAddress } from "./address"; import { Caip5 } from "./caip5"; import { decodeAmount, parseTxsResponse } from "./decode"; -import { accountToNonce, TokenInfo } from "./types"; +import { accountToNonce, BankToken } from "./types"; const { toHex } = Encoding; @@ -69,7 +69,10 @@ function buildQueryString({ return components.filter(Boolean).join("&"); } -export type TokenConfiguration = ReadonlyArray; +export interface TokenConfiguration { + /** Supported tokens of the Cosmos SDK bank module */ + readonly bank: ReadonlyArray; +} export class CosmWasmConnection implements BlockchainConnection { // we must know prefix and tokens a priori to understand the chain @@ -91,7 +94,7 @@ export class CosmWasmConnection implements BlockchainConnection { private readonly restClient: RestClient; private readonly chainData: ChainData; private readonly addressPrefix: CosmosAddressBech32Prefix; - private readonly tokenInfo: readonly TokenInfo[]; + private readonly bankTokens: readonly BankToken[]; // these are derived from arguments (cached for use in multiple functions) private readonly primaryToken: Token; @@ -106,9 +109,9 @@ export class CosmWasmConnection implements BlockchainConnection { this.restClient = restClient; this.chainData = chainData; this.addressPrefix = addressPrefix; - this.tokenInfo = tokens; + this.bankTokens = tokens.bank; - this.supportedTokens = tokens.map(info => ({ + this.supportedTokens = tokens.bank.map(info => ({ tokenTicker: info.ticker as TokenTicker, tokenName: info.name, fractionalDigits: info.fractionalDigits, @@ -152,13 +155,13 @@ export class CosmWasmConnection implements BlockchainConnection { return undefined; } const supportedCoins = account.coins.filter(({ denom }) => - this.tokenInfo.find(token => token.denom === denom), + this.bankTokens.find(token => token.denom === denom), ); const pubkey = !account.public_key ? undefined : decodeCosmosPubkey(account.public_key); return { address: address, - balance: supportedCoins.map(coin => decodeAmount(this.tokenInfo, coin)), + balance: supportedCoins.map(coin => decodeAmount(this.bankTokens, coin)), pubkey: pubkey, }; } @@ -321,6 +324,6 @@ export class CosmWasmConnection implements BlockchainConnection { // this is technically not the proper nonce. maybe this causes issues for sig validation? // leaving for now unless it causes issues const sequence = (accountForHeight.result.value.sequence - 1) as Nonce; - return parseTxsResponse(chainId, parseInt(response.height, 10), sequence, response, this.tokenInfo); + return parseTxsResponse(chainId, parseInt(response.height, 10), sequence, response, this.bankTokens); } } diff --git a/packages/bcp/src/cosmwasmconnector.ts b/packages/bcp/src/cosmwasmconnector.ts index 61aaf006..64889ac2 100644 --- a/packages/bcp/src/cosmwasmconnector.ts +++ b/packages/bcp/src/cosmwasmconnector.ts @@ -13,7 +13,7 @@ export function createCosmWasmConnector( tokens: TokenConfiguration, expectedChainId?: ChainId, ): ChainConnector { - const codec = new CosmWasmCodec(addressPrefix, tokens); + const codec = new CosmWasmCodec(addressPrefix, tokens.bank); return { establishConnection: async () => CosmWasmConnection.establish(url, addressPrefix, tokens), codec: codec, diff --git a/packages/bcp/src/decode.spec.ts b/packages/bcp/src/decode.spec.ts index fe8feb51..5f48b6f3 100644 --- a/packages/bcp/src/decode.spec.ts +++ b/packages/bcp/src/decode.spec.ts @@ -15,7 +15,7 @@ import { } from "./decode"; import { chainId, nonce, signedTxJson, txId } from "./testdata.spec"; import data from "./testdata/cosmoshub.json"; -import { TokenInfos } from "./types"; +import { BankTokens } from "./types"; const { fromBase64, fromHex } = Encoding; @@ -52,7 +52,7 @@ describe("decode", () => { }, gasLimit: "200000", }; - const defaultTokens: TokenInfos = [ + const defaultTokens: BankTokens = [ { fractionalDigits: 6, ticker: "ATOM", diff --git a/packages/bcp/src/decode.ts b/packages/bcp/src/decode.ts index c68325ba..03b1b7fd 100644 --- a/packages/bcp/src/decode.ts +++ b/packages/bcp/src/decode.ts @@ -19,7 +19,7 @@ import { } from "@iov/bcp"; import { Decimal, Encoding } from "@iov/encoding"; -import { TokenInfos } from "./types"; +import { BankTokens } from "./types"; const { fromBase64 } = Encoding; @@ -52,7 +52,7 @@ export function decodeFullSignature(signature: types.StdSignature, nonce: number }; } -export function coinToDecimal(tokens: TokenInfos, coin: types.Coin): readonly [Decimal, string] { +export function coinToDecimal(tokens: BankTokens, coin: types.Coin): readonly [Decimal, string] { const match = tokens.find(({ denom }) => denom === coin.denom); if (!match) { throw Error(`unknown denom: ${coin.denom}`); @@ -61,7 +61,7 @@ export function coinToDecimal(tokens: TokenInfos, coin: types.Coin): readonly [D return [value, match.ticker]; } -export function decodeAmount(tokens: TokenInfos, coin: types.Coin): Amount { +export function decodeAmount(tokens: BankTokens, coin: types.Coin): Amount { const [value, ticker] = coinToDecimal(tokens, coin); return { quantity: value.atomics, @@ -70,7 +70,7 @@ export function decodeAmount(tokens: TokenInfos, coin: types.Coin): Amount { }; } -export function parseMsg(msg: types.Msg, chainId: ChainId, tokens: TokenInfos): UnsignedTransaction { +export function parseMsg(msg: types.Msg, chainId: ChainId, tokens: BankTokens): UnsignedTransaction { if (types.isMsgSend(msg)) { if (msg.value.amount.length !== 1) { throw new Error("Only MsgSend with one amount is supported"); @@ -93,7 +93,7 @@ export function parseMsg(msg: types.Msg, chainId: ChainId, tokens: TokenInfos): } } -export function parseFee(fee: types.StdFee, tokens: TokenInfos): Fee { +export function parseFee(fee: types.StdFee, tokens: BankTokens): Fee { if (fee.amount.length !== 1) { throw new Error("Only fee with one amount is supported"); } @@ -107,7 +107,7 @@ export function parseTx( txValue: types.StdTx, chainId: ChainId, nonce: Nonce, - tokens: TokenInfos, + tokens: BankTokens, ): SignedTransaction { if (!types.isAminoStdTx(txValue)) { throw new Error("Only Amino StdTx is supported"); @@ -138,7 +138,7 @@ export function parseTxsResponse( currentHeight: number, nonce: Nonce, response: TxsResponse, - tokens: TokenInfos, + tokens: BankTokens, ): ConfirmedAndSignedTransaction { const height = parseInt(response.height, 10); return { diff --git a/packages/bcp/src/encode.spec.ts b/packages/bcp/src/encode.spec.ts index b81e1c37..436430d5 100644 --- a/packages/bcp/src/encode.spec.ts +++ b/packages/bcp/src/encode.spec.ts @@ -21,7 +21,7 @@ import { encodeFullSignature, encodePubkey, } from "./encode"; -import { TokenInfos } from "./types"; +import { BankTokens } from "./types"; const { fromBase64 } = Encoding; @@ -41,7 +41,7 @@ describe("encode", () => { tokenTicker: atom, }; const defaultMemo = "hello cosmos hub"; - const defaultTokens: TokenInfos = [ + const defaultTokens: BankTokens = [ { fractionalDigits: 6, ticker: "ATOM", diff --git a/packages/bcp/src/encode.ts b/packages/bcp/src/encode.ts index 479895b2..a0a84d8e 100644 --- a/packages/bcp/src/encode.ts +++ b/packages/bcp/src/encode.ts @@ -12,7 +12,7 @@ import { } from "@iov/bcp"; import { Decimal, Encoding } from "@iov/encoding"; -import { TokenInfos } from "./types"; +import { BankTokens } from "./types"; const { toBase64 } = Encoding; @@ -33,7 +33,7 @@ export function encodePubkey(pubkey: PubkeyBundle): types.PubKey { } } -export function decimalToCoin(lookup: TokenInfos, value: Decimal, ticker: string): types.Coin { +export function decimalToCoin(lookup: BankTokens, value: Decimal, ticker: string): types.Coin { const match = lookup.find(token => token.ticker === ticker); if (!match) { throw Error(`unknown ticker: ${ticker}`); @@ -49,7 +49,7 @@ export function decimalToCoin(lookup: TokenInfos, value: Decimal, ticker: string }; } -export function encodeAmount(amount: Amount, tokens: TokenInfos): types.Coin { +export function encodeAmount(amount: Amount, tokens: BankTokens): types.Coin { return decimalToCoin( tokens, Decimal.fromAtomics(amount.quantity, amount.fractionalDigits), @@ -57,7 +57,7 @@ export function encodeAmount(amount: Amount, tokens: TokenInfos): types.Coin { ); } -export function encodeFee(fee: Fee, tokens: TokenInfos): types.StdFee { +export function encodeFee(fee: Fee, tokens: BankTokens): types.StdFee { if (fee.tokens === undefined) { throw new Error("Cannot encode fee without tokens"); } @@ -79,7 +79,7 @@ export function encodeFullSignature(fullSignature: FullSignature): types.StdSign } } -export function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): types.AminoTx { +export function buildUnsignedTx(tx: UnsignedTransaction, tokens: BankTokens): types.AminoTx { if (!isSendTransaction(tx)) { throw new Error("Received transaction of unsupported kind"); } @@ -108,7 +108,7 @@ export function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): ty }; } -export function buildSignedTx(tx: SignedTransaction, tokens: TokenInfos): types.AminoTx { +export function buildSignedTx(tx: SignedTransaction, tokens: BankTokens): types.AminoTx { const built = buildUnsignedTx(tx.transaction, tokens); return { ...built, diff --git a/packages/bcp/src/types.ts b/packages/bcp/src/types.ts index 71ec2be8..6c3fcc22 100644 --- a/packages/bcp/src/types.ts +++ b/packages/bcp/src/types.ts @@ -1,7 +1,7 @@ import { types } from "@cosmwasm/sdk"; import { Nonce } from "@iov/bcp"; -export interface TokenInfo { +export interface BankToken { readonly denom: string; readonly ticker: string; /** @@ -16,7 +16,7 @@ export interface TokenInfo { readonly fractionalDigits: number; } -export type TokenInfos = ReadonlyArray; +export type BankTokens = ReadonlyArray; // tslint:disable-next-line:no-bitwise const maxAcct = 1 << 23; diff --git a/packages/bcp/types/cosmwasmconnection.d.ts b/packages/bcp/types/cosmwasmconnection.d.ts index 413795dc..2413e93d 100644 --- a/packages/bcp/types/cosmwasmconnection.d.ts +++ b/packages/bcp/types/cosmwasmconnection.d.ts @@ -22,11 +22,14 @@ import { } from "@iov/bcp"; import { Stream } from "xstream"; import { TokenInfo } from "./types"; -export declare type TokenConfiguration = ReadonlyArray< - TokenInfo & { - readonly name: string; - } ->; +export interface TokenConfiguration { + /** Supported tokens of the Cosmos SDK bank module */ + readonly bank: ReadonlyArray< + TokenInfo & { + readonly name: string; + } + >; +} export declare class CosmWasmConnection implements BlockchainConnection { static establish( url: string, @@ -37,7 +40,7 @@ export declare class CosmWasmConnection implements BlockchainConnection { private readonly restClient; private readonly chainData; private readonly addressPrefix; - private readonly tokenInfo; + private readonly bankTokens; private readonly primaryToken; private readonly supportedTokens; private constructor();