From 549c5a0221be3e92b7d9b451e83782653392d88c Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 24 Feb 2021 08:38:39 +0100 Subject: [PATCH] Bring back support for pbjs generated types --- packages/proto-signing/src/index.ts | 10 ++- packages/proto-signing/src/registry.spec.ts | 86 +++++++++++++-------- packages/proto-signing/src/registry.ts | 47 ++++++++++- 3 files changed, 108 insertions(+), 35 deletions(-) diff --git a/packages/proto-signing/src/index.ts b/packages/proto-signing/src/index.ts index b975b396..0b11ea43 100644 --- a/packages/proto-signing/src/index.ts +++ b/packages/proto-signing/src/index.ts @@ -1,4 +1,12 @@ -export { EncodeObject, GeneratedType, Registry } from "./registry"; +export { + isPbjsGeneratedType, + isTsProtoGeneratedType, + EncodeObject, + GeneratedType, + Registry, + TsProtoGeneratedType, + PbjsGeneratedType, +} from "./registry"; export { DirectSecp256k1HdWallet } from "./directsecp256k1hdwallet"; export { DirectSecp256k1Wallet } from "./directsecp256k1wallet"; export { makeCosmoshubPath } from "./paths"; diff --git a/packages/proto-signing/src/registry.spec.ts b/packages/proto-signing/src/registry.spec.ts index 4af159ed..274dac87 100644 --- a/packages/proto-signing/src/registry.spec.ts +++ b/packages/proto-signing/src/registry.spec.ts @@ -1,17 +1,23 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { fromHex } from "@cosmjs/encoding"; import { assert } from "@cosmjs/utils"; import Long from "long"; +import { Field, Type } from "protobufjs"; import { MsgSend as IMsgSend } from "./codec/cosmos/bank/v1beta1/tx"; import { TxBody } from "./codec/cosmos/tx/v1beta1/tx"; import { Any } from "./codec/google/protobuf/any"; -import { Registry } from "./registry"; +import { isPbjsGeneratedType, isTsProtoGeneratedType, Registry } from "./registry"; describe("registry demo", () => { it("works with a default msg", () => { const registry = new Registry(); - const Coin = registry.lookupType("/cosmos.base.v1beta1.Coin")!; - const MsgSend = registry.lookupType("/cosmos.bank.v1beta1.MsgSend")!; + const Coin = registry.lookupType("/cosmos.base.v1beta1.Coin"); + const MsgSend = registry.lookupType("/cosmos.bank.v1beta1.MsgSend"); + assert(Coin); + assert(MsgSend); + assert(isTsProtoGeneratedType(Coin)); + assert(isTsProtoGeneratedType(MsgSend)); const coin = Coin.fromPartial({ denom: "ucosm", @@ -49,35 +55,53 @@ describe("registry demo", () => { expect(msgSendDecoded.amount).toEqual(msgSend.amount); }); - // TODO: Can't autogenerate types from TS code using ts-proto - // it("works with a custom msg", () => { - // const typeUrl = "/demo.MsgDemo"; - // const registry = new Registry([[typeUrl, MsgDemoType]]); - // const MsgDemo = registry.lookupType(typeUrl)!; + it("works with a custom msg", () => { + // From https://gist.github.com/fadeev/a4981eff1cf3a805ef10e25313d5f2b7 + const typeUrl = "/blog.MsgCreatePost"; + const MsgCreatePostOriginal = new Type("MsgCreatePost") + .add(new Field("creator", 1, "string")) + .add(new Field("title", 2, "string")) + .add(new Field("body", 3, "string")) + .add(new Field("attachment", 4, "bytes")); - // const msgDemo = MsgDemo.fromPartial({ - // example: "Some example text", - // }); - // const msgDemoBytes = MsgDemo.encode(msgDemo).finish(); - // const msgDemoWrapped = Any.create({ - // type_url: typeUrl, - // value: msgDemoBytes, - // }); - // const txBody = TxBody.create({ - // messages: [msgDemoWrapped], - // memo: "Some memo", - // timeoutHeight: Long.fromNumber(9999), - // extensionOptions: [], - // }); - // const txBodyBytes = TxBody.encode(txBody).finish(); + const registry = new Registry([[typeUrl, MsgCreatePostOriginal]]); + const MsgCreatePost = registry.lookupType(typeUrl); + assert(MsgCreatePost); + assert(isPbjsGeneratedType(MsgCreatePost)); - // const txBodyDecoded = TxBody.decode(txBodyBytes); - // const msg = txBodyDecoded.messages[0]; - // assert(msg.type_url); - // assert(msg.value); + const msgDemo = MsgCreatePost.create({ + creator: "Me", + title: "Something with stars", + body: "la la la", + attachment: fromHex("AABBAABB66FE"), + }); + const msgDemoBytes = MsgCreatePost.encode(msgDemo).finish(); + const msgDemoWrapped = Any.fromPartial({ + typeUrl: typeUrl, + value: msgDemoBytes, + }); + const txBody = TxBody.fromPartial({ + messages: [msgDemoWrapped], + memo: "Some memo", + timeoutHeight: Long.fromNumber(9999), + extensionOptions: [], + }); + const txBodyBytes = TxBody.encode(txBody).finish(); - // const decoder = registry.lookupType(msg.type_url)!; - // const msgDemoDecoded = decoder.decode(msg.value); - // expect(msgDemoDecoded.example).toEqual(msgDemo.example); - // }); + const txBodyDecoded = TxBody.decode(txBodyBytes); + const msg = txBodyDecoded.messages[0]; + assert(msg.typeUrl); + assert(msg.value); + + const decoder = registry.lookupType(msg.typeUrl)!; + const msgDemoDecoded = decoder.decode(msg.value); + expect(msgDemoDecoded).toEqual( + jasmine.objectContaining({ + creator: "Me", + title: "Something with stars", + body: "la la la", + }), + ); + expect(Uint8Array.from(msgDemoDecoded.attachment)).toEqual(fromHex("AABBAABB66FE")); + }); }); diff --git a/packages/proto-signing/src/registry.ts b/packages/proto-signing/src/registry.ts index 63319964..aada6a07 100644 --- a/packages/proto-signing/src/registry.ts +++ b/packages/proto-signing/src/registry.ts @@ -7,7 +7,10 @@ import { Coin } from "./codec/cosmos/base/v1beta1/coin"; import { TxBody } from "./codec/cosmos/tx/v1beta1/tx"; import { Any } from "./codec/google/protobuf/any"; -export interface GeneratedType { +/** + * A type generated by [ts-proto](https://github.com/stephenh/ts-proto). + */ +export interface TsProtoGeneratedType { readonly encode: (message: any | { [k: string]: any }, writer?: protobuf.Writer) => protobuf.Writer; readonly decode: (input: Uint8Array | protobuf.Reader, length?: number) => any; readonly fromJSON: (object: { [k: string]: any }) => any; @@ -15,6 +18,28 @@ export interface GeneratedType { readonly toJSON: (message: any | { [k: string]: any }) => unknown; } +/** + * A type generated by [protobufjs](https://github.com/protobufjs/protobuf.js). + * + * This can be used if you want to create types at runtime using pure JavaScript. + * See https://gist.github.com/fadeev/a4981eff1cf3a805ef10e25313d5f2b7 + */ +export interface PbjsGeneratedType { + readonly create: (properties?: { [k: string]: any }) => any; + readonly encode: (message: any | { [k: string]: any }, writer?: protobuf.Writer) => protobuf.Writer; + readonly decode: (reader: protobuf.Reader | Uint8Array, length?: number) => any; +} + +export type GeneratedType = TsProtoGeneratedType | PbjsGeneratedType; + +export function isTsProtoGeneratedType(type: GeneratedType): type is TsProtoGeneratedType { + return typeof (type as TsProtoGeneratedType).fromPartial === "function"; +} + +export function isPbjsGeneratedType(type: GeneratedType): type is PbjsGeneratedType { + return !isTsProtoGeneratedType(type); +} + export interface EncodeObject { readonly typeUrl: string; readonly value: any; @@ -56,6 +81,22 @@ export class Registry { this.types.set(typeUrl, type); } + /** + * Looks up a type that was previously added to the registry. + * + * The generator information (ts-proto or pbjs) gets lost along the way. + * If you need to work with the result type in TypeScript, you can use: + * + * ``` + * import { assert } from "@cosmjs/utils"; + * + * const Coin = registry.lookupType("/cosmos.base.v1beta1.Coin"); + * assert(Coin); // Ensures not unset + * assert(isTsProtoGeneratedType(Coin)); // Ensures this is the type we expect + * + * // Coin is typed TsProtoGeneratedType now. + * ``` + */ public lookupType(typeUrl: string): GeneratedType | undefined { return this.types.get(typeUrl); } @@ -73,8 +114,8 @@ export class Registry { return this.encodeTxBody(value); } const type = this.lookupTypeWithError(typeUrl); - const created = type.fromPartial(value); - return Uint8Array.from(type.encode(created).finish()); + const instance = isTsProtoGeneratedType(type) ? type.fromPartial(value) : type.create(value); + return Uint8Array.from(type.encode(instance).finish()); } public encodeTxBody(txBodyFields: TxBodyValue): Uint8Array {