From 548cfbefb89903442054faed6f636a9cf0d5fd3b Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 4 Jun 2019 11:45:56 +0200 Subject: [PATCH] Construct toRawType using internal types (#966) * Construct toRawType using internal types * typo * Struct test for with + toRawType * Tuple tests for toRawType * Remove extra empty check, already handled * EnumType with sane toRawType * Sane toRawType representation for Set * Use JSON.stringify inside Struct toRawType * Extrinsic toRawType yields Extrinsic (like a primitive) * Block toRawType test --- packages/types/src/codec/EnumType.spec.ts | 14 ++++++++ packages/types/src/codec/EnumType.ts | 19 +++++------ packages/types/src/codec/Option.ts | 6 +++- packages/types/src/codec/Set.spec.ts | 8 +++++ packages/types/src/codec/Set.ts | 6 +--- packages/types/src/codec/Struct.spec.ts | 39 +++++++++++++++++++++++ packages/types/src/codec/Struct.ts | 29 +++++++++-------- packages/types/src/codec/Tuple.spec.ts | 14 ++++++++ packages/types/src/codec/Tuple.ts | 8 ++--- packages/types/src/rpc/Block.spec.ts | 34 ++++++++++++++++++++ packages/types/src/type/Balance.ts | 9 ++++++ packages/types/src/type/Extrinsic.ts | 8 +++++ packages/types/src/type/ExtrinsicEra.ts | 8 +++-- 13 files changed, 164 insertions(+), 38 deletions(-) create mode 100644 packages/types/src/rpc/Block.spec.ts diff --git a/packages/types/src/codec/EnumType.spec.ts b/packages/types/src/codec/EnumType.spec.ts index 2f5796abc9..c7beb2276e 100644 --- a/packages/types/src/codec/EnumType.spec.ts +++ b/packages/types/src/codec/EnumType.spec.ts @@ -211,4 +211,18 @@ describe('Enum', () => { }); }); }); + + describe('toRawType', () => { + it('has a sane output for basic enums', () => { + expect( + new Enum(['foo', 'bar']).toRawType() + ).toEqual(JSON.stringify({ _enum: ['foo', 'bar'] })); + }); + + it('has a sane output for types enums', () => { + expect( + new Enum({ foo: Text, bar: U32 }).toRawType() + ).toEqual(JSON.stringify({ _enum: { foo: 'Text', bar: 'u32' } })); + }); + }); }); diff --git a/packages/types/src/codec/EnumType.ts b/packages/types/src/codec/EnumType.ts index 2ea8d3f269..d1aadd3095 100644 --- a/packages/types/src/codec/EnumType.ts +++ b/packages/types/src/codec/EnumType.ts @@ -222,18 +222,15 @@ export default class Enum extends Base implements Codec { * @description Returns the base runtime type name for this instance */ toRawType (): string { - const kv = this._isBasic - ? Object.keys(this._def).map((key) => - `"${key}"` - ) - : Object.entries(this._def).map(([key, Type]) => - `"${key}":"${new Type().toRawType()}"` - ); - const braces = this._isBasic - ? '[]' - : '{}'; + const _enum = this._isBasic + ? Object.keys(this._def) + : Object.entries(this._def).reduce((result, [key, Type]) => { + result[key] = new Type().toRawType(); - return `{"_enum":${braces[0]}${kv.join(',')}${braces[1]}}`; + return result; + }, {} as { [index: string]: string }); + + return JSON.stringify({ _enum }); } /** diff --git a/packages/types/src/codec/Option.ts b/packages/types/src/codec/Option.ts index 2142691eaa..d8f08d81c3 100644 --- a/packages/types/src/codec/Option.ts +++ b/packages/types/src/codec/Option.ts @@ -17,10 +17,14 @@ import Null from '../primitive/Null'; * with a value if/as required/found. */ export default class Option extends Base implements Codec { + private _Type: Constructor; + constructor (Type: Constructor, value?: any) { super( Option.decodeOption(Type, value) ); + + this._Type = Type; } static decodeOption (Type: Constructor, value?: any): Codec { @@ -115,7 +119,7 @@ export default class Option extends Base implements Codec { * @description Returns the base runtime type name for this instance */ toRawType (): string { - return `Option<${this.raw.toRawType()}>`; + return `Option<${new this._Type().toRawType()}>`; } /** diff --git a/packages/types/src/codec/Set.spec.ts b/packages/types/src/codec/Set.spec.ts index fbd53e98ed..aa91164634 100644 --- a/packages/types/src/codec/Set.spec.ts +++ b/packages/types/src/codec/Set.spec.ts @@ -91,4 +91,12 @@ describe('Set', () => { ).toBe(false); }); }); + + it('has a sane toRawType representation', () => { + expect( + new Set({ a: 1, b: 2, c: 345 }).toRawType() + ).toEqual(JSON.stringify({ + _set: { a: 1, b: 2, c: 345 } + })); + }); }); diff --git a/packages/types/src/codec/Set.ts b/packages/types/src/codec/Set.ts index fef655e2dd..853fe6ab82 100644 --- a/packages/types/src/codec/Set.ts +++ b/packages/types/src/codec/Set.ts @@ -149,12 +149,8 @@ export default class CodecSet extends Set implements Codec { * @description Returns the base runtime type name for this instance */ toRawType (): string { - const kv = Object.entries(this._setValues).map(([key, value]) => - `"${key}":${value}` // JSON format - ); - // FIXME We don't cater for this in createType as of yet - return `{"_set":{${kv.join(',')}}}`; + return JSON.stringify({ _set: this._setValues }); } /** diff --git a/packages/types/src/codec/Struct.spec.ts b/packages/types/src/codec/Struct.spec.ts index 715ffaa67a..0cb0419d56 100644 --- a/packages/types/src/codec/Struct.spec.ts +++ b/packages/types/src/codec/Struct.spec.ts @@ -2,10 +2,13 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import AccountId from '../primitive/AccountId'; import Text from '../primitive/Text'; import U32 from '../primitive/U32'; +import Balance from '../type/Balance'; import BlockNumber from '../type/BlockNumber'; import { CodecTo } from '../types'; +import Compact from './Compact'; import Option from './Option'; import Struct from './Struct'; import Vector from './Vector'; @@ -183,4 +186,40 @@ describe('Struct', () => { }, { blockNumber: '0x1234567890abcdef' }).toString() ).toEqual('{"blockNumber":"0x1234567890abcdef"}'); }); + + it('generates sane toRawType', () => { + expect( + new Struct({ + accountId: AccountId, + balance: Balance, + blockNumber: BlockNumber, + compactNumber: Compact.with(BlockNumber), + optionNumber: Option.with(BlockNumber), + counter: U32, + vector: Vector.with(AccountId) + }).toRawType() + ).toEqual(JSON.stringify({ + accountId: 'AccountId', + balance: 'Balance', + blockNumber: 'u64', + compactNumber: 'Compact', + optionNumber: 'Option', + counter: 'u32', + vector: 'Vec' + })); + }); + + it('generates sane toRawType (via with)', () => { + const Type = Struct.with({ + accountId: AccountId, + balance: Balance + }); + + expect( + new Type().toRawType() + ).toEqual(JSON.stringify({ + accountId: 'AccountId', + balance: 'Balance' + })); + }); }); diff --git a/packages/types/src/codec/Struct.ts b/packages/types/src/codec/Struct.ts index 3b937d04d2..393384b079 100644 --- a/packages/types/src/codec/Struct.ts +++ b/packages/types/src/codec/Struct.ts @@ -28,7 +28,7 @@ export default class Struct< E extends { [K in keyof S]: string } = { [K in keyof S]: string } > extends Map implements Codec { protected _jsonMap: Map; - protected _Types: E; + protected _Types: S; constructor (Types: S, value: V | Map | Array = {} as V, jsonMap: Map = new Map()) { const decoded = Struct.decodeStruct(Types, value, jsonMap); @@ -38,14 +38,7 @@ export default class Struct< ); this._jsonMap = jsonMap; - this._Types = (Object - .keys(Types) as Array) - .reduce((result: E, key) => { - // TS2322: Type 'string' is not assignable to type 'E[keyof S]'. - (result as any)[key] = Types[key].name; - - return result; - }, {} as E); + this._Types = Types; } /** @@ -170,7 +163,13 @@ export default class Struct< * @description Returns the Type description to sthe structure */ get Type (): E { - return this._Types; + return (Object + .entries(this._Types) as Array<[keyof S, Constructor]>) + .reduce((result: E, [key, Type]) => { + (result as any)[key] = Type.name; + + return result; + }, {} as E); } /** @@ -236,11 +235,13 @@ export default class Struct< * @description Returns the base runtime type name for this instance */ toRawType (): string { - const kv = [...this.entries()].map(([key, value]) => - `"${key}":"${value.toRawType()}"` // double-quotes, JSON - ); + return JSON.stringify( + Object.entries(this._Types).reduce((result, [key, Type]) => { + result[key] = new Type().toRawType(); - return `{${kv.join(',')}}`; + return result; + }, {} as { [index: string]: string }) + ); } /** diff --git a/packages/types/src/codec/Tuple.spec.ts b/packages/types/src/codec/Tuple.spec.ts index 5a0c793a84..9231f62b12 100644 --- a/packages/types/src/codec/Tuple.spec.ts +++ b/packages/types/src/codec/Tuple.spec.ts @@ -94,4 +94,18 @@ describe('Tuple', () => { expect(tuple.eq(['bazzing', 72])).toBe(false); }); }); + + describe('toRawType', () => { + it('generates sane value with array types', () => { + expect( + new Tuple([U32, BlockNumber]).toRawType() + ).toEqual('(u32,u64)'); + }); + + it('generates sane value with object types', () => { + expect( + new Tuple({ number: U32, blockNumber: BlockNumber }).toRawType() + ).toEqual('(u32,u64)'); + }); + }); }); diff --git a/packages/types/src/codec/Tuple.ts b/packages/types/src/codec/Tuple.ts index 8cf01b019d..f1fc4a832f 100644 --- a/packages/types/src/codec/Tuple.ts +++ b/packages/types/src/codec/Tuple.ts @@ -21,7 +21,7 @@ type TupleConstructors = Array | { export default class Tuple extends AbstractArray { private _Types: TupleConstructors; - constructor (Types: TupleConstructors, value: any) { + constructor (Types: TupleConstructors, value?: any) { super( ...Tuple.decodeTuple(Types, value) ); @@ -83,9 +83,9 @@ export default class Tuple extends AbstractArray { toRawType (): string { const types = ( Array.isArray(this._Types) - ? this._Types.map((Type) => new Type().toRawType()) - : Object.keys(this._Types) - ); + ? this._Types + : Object.values(this._Types) + ).map((Type) => new Type().toRawType()); return `(${types.join(',')})`; } diff --git a/packages/types/src/rpc/Block.spec.ts b/packages/types/src/rpc/Block.spec.ts new file mode 100644 index 0000000000..ecaf646821 --- /dev/null +++ b/packages/types/src/rpc/Block.spec.ts @@ -0,0 +1,34 @@ +// Copyright 2017-2019 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import extrinsics from '@polkadot/extrinsics/static'; + +import Method from '../primitive/Method'; +import Block from './Block'; + +describe('Block', () => { + beforeEach(() => { + Method.injectMethods(extrinsics); + }); + + it('has a valid toRawType', () => { + expect( + new Block().toRawType() + ).toEqual( + // each of the containing structures have been stringified on their own + JSON.stringify({ + header: JSON.stringify({ + parentHash: 'H256', + number: 'Compact', + stateRoot: 'H256', + extrinsicsRoot: 'H256', + digest: JSON.stringify({ + logs: 'Vec<{"_enum":{"Other":"Bytes","AuthoritiesChange":"Vec","ChangesTrieRoot":"H256","Seal":"(u64,H512)","Consensus":"(u32,Bytes)"}}>' + }) + }), + extrinsics: 'Vec' + }) + ); + }); +}); diff --git a/packages/types/src/type/Balance.ts b/packages/types/src/type/Balance.ts index 1f25301f09..04e29c15c0 100644 --- a/packages/types/src/type/Balance.ts +++ b/packages/types/src/type/Balance.ts @@ -10,6 +10,15 @@ import U128 from '../primitive/U128'; * The Substrate Balance representation as a [[U128]]. */ export default class Balance extends U128 { + /** + * @description Returns the base runtime type name for this instance + */ + toRawType (): string { + // NOTE Balance we treat as a special case, don't return the base u128 - there + // is typically special processing downstream, i.e. different inputs, it gets + // treated as a primitive in this case + return 'Balance'; + } } /** diff --git a/packages/types/src/type/Extrinsic.ts b/packages/types/src/type/Extrinsic.ts index 97a0f31c86..1157f195f7 100644 --- a/packages/types/src/type/Extrinsic.ts +++ b/packages/types/src/type/Extrinsic.ts @@ -197,6 +197,14 @@ export default class Extrinsic extends Struct implements IExtrinsic { return this.toHex(); } + /** + * @description Returns the base runtime type name for this instance + */ + toRawType (): string { + // We are treating this in the same way we do a primitive, this is known + return 'Extrinsic'; + } + /** * @description Encodes the value as a Uint8Array as per the SCALE specifications * @param isBare true when the value has none of the type-specific prefixes (internal) diff --git a/packages/types/src/type/ExtrinsicEra.ts b/packages/types/src/type/ExtrinsicEra.ts index 1b0e712c81..c8cbd04375 100644 --- a/packages/types/src/type/ExtrinsicEra.ts +++ b/packages/types/src/type/ExtrinsicEra.ts @@ -111,9 +111,9 @@ export default class ExtrinsicEra extends Enum implements IExtrinsicEra { */ export class ImmortalEra extends U8a { constructor (value?: AnyU8a) { - super(value); - - assert(this.eq(VALID_IMMORTAL), `IMMORTAL: expected ${VALID_IMMORTAL.toHex()}, found ${this.toHex()}`); + // For immortals, we always provide the known value (i.e. treated as a + // constant no matter how it is constructed - it is a fixed structure) + super(VALID_IMMORTAL); } } @@ -155,6 +155,8 @@ export class MortalEra extends Tuple { const quantizedPhase = phase / quantizeFactor * quantizeFactor; return [new U64(calPeriod), new U64(quantizedPhase)]; + } else if (!value) { + return [new U64(), new U64()]; } throw new Error('Invalid data passed to Mortal era');