diff --git a/packages/api/test/e2e/promise-queries.spec.js b/packages/api/test/e2e/promise-queries.spec.js index 8b9aa3fa5c..a9502df33d 100644 --- a/packages/api/test/e2e/promise-queries.spec.js +++ b/packages/api/test/e2e/promise-queries.spec.js @@ -2,6 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import BN from 'bn.js'; import testingPairs from '@polkadot/keyring/testingPairs'; import Api from '../../src/promise'; @@ -34,7 +35,7 @@ describe.skip('e2e queries', () => { ).toBe(false); }); - it('subscribes to queries', (done) => { + it('subscribes to rpc', (done) => { api.rpc.chain.subscribeNewHead((header) => { expect(header.blockNumber.isZero()).toBe(false); @@ -42,6 +43,22 @@ describe.skip('e2e queries', () => { }); }); + it('subscribes to queries', (done) => { + api.query.system.accountNonce(keyring.ferdie.address(), (nonce) => { + expect(nonce instanceof BN).toBe(true); + + done(); + }); + }); + + it('subscribes to queries (default)', (done) => { + api.query.staking.validatorPreferences(keyring.ferdie.address(), (prefs) => { + expect(prefs.unstakeThreshold.toNumber()).toBe(12); + + done(); + }); + }); + it('makes a query at a specific block', async () => { const header = await api.rpc.chain.getHeader(); const events = await api.query.system.events.at(header.hash); diff --git a/packages/rpc-core/src/index.ts b/packages/rpc-core/src/index.ts index 77decaabaf..f86b1d0a55 100644 --- a/packages/rpc-core/src/index.ts +++ b/packages/rpc-core/src/index.ts @@ -208,8 +208,10 @@ export default class Rpc implements RpcInterface { !item ? undefined : ( + // for `null` we fallback to the default value, or create an empty type, + // otherwise we return the actual value as retrieved item.value.isNone - ? null + ? createType(type, (_key.meta || { default: undefined }).default) : createType(type, item.value.unwrap()) ) ); diff --git a/packages/types/src/Bytes.ts b/packages/types/src/Bytes.ts index 265e9eaf0f..b79f8ceeea 100644 --- a/packages/types/src/Bytes.ts +++ b/packages/types/src/Bytes.ts @@ -16,11 +16,11 @@ import U8a from './codec/U8a'; * as what is found in [[Text]] and [[Vector]]) */ export default class Bytes extends U8a { - constructor (value: AnyU8a) { + constructor (value?: AnyU8a) { super(Bytes.decodeBytes(value)); } - private static decodeBytes (value: AnyU8a): Uint8Array { + private static decodeBytes (value?: AnyU8a): Uint8Array | undefined { // FIXME Cyclic dependency, however needed for the StoreageData check below. In a perfect // world, we should probably be checking Bytes - however as a first step, check against // StorageData to cater for the _specific_ problematic case diff --git a/packages/types/src/Event.ts b/packages/types/src/Event.ts index 8c4e090a83..bfcebfcc3f 100644 --- a/packages/types/src/Event.ts +++ b/packages/types/src/Event.ts @@ -12,6 +12,7 @@ import U8aFixed from './codec/U8aFixed'; import { TypeDef, getTypeClass, getTypeDef } from './codec/createType'; import Metadata from './Metadata'; import { EventMetadata } from './Metadata/Events'; +import Null from './Null'; const EventTypes: { [index: string]: Constructor } = {}; @@ -85,7 +86,7 @@ export class EventIndex extends U8aFixed { export default class Event extends Struct { // Currently we _only_ decode from Uint8Array, since we expect it to // be used via EventRecord - constructor (_value: Uint8Array) { + constructor (_value?: Uint8Array) { const { DataType, value } = Event.decodeEvent(_value); super({ @@ -94,12 +95,17 @@ export default class Event extends Struct { }, value); } - static decodeEvent (value: Uint8Array) { + static decodeEvent (value: Uint8Array = new Uint8Array()) { const index = value.subarray(0, 2); const DataType = EventTypes[index.toString()]; if (isUndefined(DataType)) { - throw new Error(`Unable to decode event for index ${u8aToHex(index)}`); + console.error(`Unable to decode event for index ${u8aToHex(index)}`); + + return { + DataType: Null, + value: undefined + }; } return { diff --git a/packages/types/src/Metadata/Metadata.spec.ts b/packages/types/src/Metadata/Metadata.spec.ts index 0bf018225d..89b292a7ca 100644 --- a/packages/types/src/Metadata/Metadata.spec.ts +++ b/packages/types/src/Metadata/Metadata.spec.ts @@ -2,19 +2,44 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import extrinsicsFromMeta from '@polkadot/extrinsics/fromMetadata'; + +import createType from '../codec/createType'; +import Method from '../Method'; + import Metadata from './index'; import latestParsed from './latest.substrate.json'; import rpcData from './static'; describe('Metadata', () => { it('decodes latest properly', () => { - const decoded = new Metadata(rpcData); - const str = JSON.stringify(decoded.toJSON()); + const metadata = new Metadata(rpcData); + const str = JSON.stringify(metadata.toJSON()); console.error(str); - console.error(decoded.getUniqTypes()); + console.error(metadata.getUniqTypes()); - expect(decoded.events.length).not.toBe(0); + expect(metadata.events.length).not.toBe(0); expect(str).toEqual(JSON.stringify(latestParsed)); }); + + describe('storage with default values', () => { + const metadata = new Metadata(rpcData); + + Method.injectMethods(extrinsicsFromMeta(metadata)); + + metadata.modules.forEach((mod) => { + if (mod.storage.isNone) { + return; + } + + mod.storage.unwrap().functions.forEach((fn) => { + it(`creates default types for ${mod.prefix}.${fn.name}, type ${fn.type}`, () => { + expect( + () => createType(fn.type.toString(), fn.default) + ).not.toThrow(); + }); + }); + }); + }); }); diff --git a/packages/types/src/Metadata/Modules.ts b/packages/types/src/Metadata/Modules.ts index e24c3ea4be..cd3ee4b547 100644 --- a/packages/types/src/Metadata/Modules.ts +++ b/packages/types/src/Metadata/Modules.ts @@ -2,7 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { AnyNumber, Codec } from '../types'; +import { AnyNumber } from '../types'; import Bytes from '../Bytes'; import Enum from '../codec/Enum'; @@ -13,7 +13,6 @@ import Vector from '../codec/Vector'; import Text from '../Text'; import Type from '../Type'; import U16 from '../U16'; -// import { getTypeDef, getTypeClass } from '../codec/createType'; export class FunctionArgumentMetadata extends Struct { constructor (value?: any) { @@ -195,19 +194,12 @@ type StorageFunctionMetadataValue = { name: string | Text, modifier: StorageFunctionModifier | AnyNumber, type: StorageFunctionType, - default: Codec, // More precisely, it's a `type` + default: Bytes, documentation: Vector | Array }; -export class StorageFunctionMetadata extends Struct { - // private _originalLength: number; - +export class StorageFunctionMetadata extends Struct { constructor (value?: StorageFunctionMetadataValue | Uint8Array) { - // We try to figure out the type of the storage function (default to Bytes) - // const Type = value && (value as StorageFunctionMetadataValue).type - // ? getTypeClass(getTypeDef((value as StorageFunctionMetadataValue).type.toString())) as Constructor - // : Bytes; - super({ name: Text, modifier: StorageFunctionModifier, @@ -215,36 +207,13 @@ export class StorageFunctionMetadata extends Struct { default: Bytes, documentation: Vector.with(Text) }, value); - - // this._originalLength = super.encodedLength; - - // If, after construction, we "learned" (i.e. decoded) the type, then we - // decode the `default` Bytes with the new type. - // FIXME But for now, we don't do this, just leave the hex value as-is. - // if ( - // this.get('default') instanceof Bytes && - // this.get('type')!.toString() !== 'Bytes' - // ) { - // const NewType = getTypeClass(getTypeDef(this.get('type')!.toString())); - // this.set('default', new NewType(this.get('default')) as T); - // } } - // /** - // * @description The length of the value when encoded as a Uint8Array - // */ - // get encodedLength (): number { - // // NOTE Length is used in the decoding calculations, so return the original (pre-cleanup) - // // length of the data. Since toU8a is disabled, this does not affect encoding, but rather - // // only the decoding leg, allowing the decoders to work with original pointers - // return this._originalLength; - // } - /** * @description The default value of the storage function */ - get default (): T { - return this.get('default') as T; + get default (): Bytes { + return this.get('default') as Bytes; } /** diff --git a/packages/types/src/Method.spec.ts b/packages/types/src/Method.spec.ts index 3008d188ae..69b7d566eb 100644 --- a/packages/types/src/Method.spec.ts +++ b/packages/types/src/Method.spec.ts @@ -11,12 +11,6 @@ describe('Method', () => { Method.injectMethods(extrinsics); }); - it('throws when not decodable', () => { - expect( - () => new Method('foo') - ).toThrowError(/Method: cannot decode value/); - }); - it('handles decoding correctly (bare)', () => { expect( new Method({ diff --git a/packages/types/src/Method.ts b/packages/types/src/Method.ts index 1d32636d94..37803a0a81 100644 --- a/packages/types/src/Method.ts +++ b/packages/types/src/Method.ts @@ -126,7 +126,14 @@ export default class Method extends Struct { }; } - throw new Error(`Method: cannot decode value '${value}' or type ${typeof value}`); + console.error(`Method: cannot decode value '${value}' of type ${typeof value}`); + + return { + args: new Uint8Array(), + argsDef: {}, + meta: new FunctionMetadata(), + callIndex: new Uint8Array([255, 255]) + }; } // If the extrinsic function has an argument of type `Origin`, we ignore it diff --git a/packages/types/src/StorageKey.ts b/packages/types/src/StorageKey.ts index 7f84b25122..f220495927 100644 --- a/packages/types/src/StorageKey.ts +++ b/packages/types/src/StorageKey.ts @@ -24,11 +24,13 @@ export interface StorageFunction { * constructed by passing in a raw key or a StorageFunction with (optional) arguments. */ export default class StorageKey extends Bytes { + private _meta: StorageFunctionMetadata | null; private _outputType: string | null; constructor (value: AnyU8a | StorageKey | StorageFunction | [StorageFunction, any]) { super(StorageKey.decodeStorageKey(value)); + this._meta = StorageKey.getMeta(value as StorageKey); this._outputType = StorageKey.getType(value as StorageKey); } @@ -46,6 +48,20 @@ export default class StorageKey extends Bytes { return value as Uint8Array; } + static getMeta (value: StorageKey | StorageFunction | [StorageFunction, any]): StorageFunctionMetadata | null { + if (value instanceof StorageKey) { + return value.meta; + } else if (isFunction(value)) { + return value.meta; + } else if (Array.isArray(value)) { + const [fn] = value; + + return fn.meta; + } + + return null; + } + static getType (value: StorageKey | StorageFunction | [StorageFunction, any]): string | null { if (value instanceof StorageKey) { return value.outputType; @@ -60,6 +76,13 @@ export default class StorageKey extends Bytes { return null; } + /** + * @description The metadata or `null` when not available + */ + get meta (): StorageFunctionMetadata | null { + return this._meta; + } + /** * @description The output type, `null` when not available */ diff --git a/packages/types/src/codec/AbstractInt.ts b/packages/types/src/codec/AbstractInt.ts index 05279cd792..e4a274bec0 100644 --- a/packages/types/src/codec/AbstractInt.ts +++ b/packages/types/src/codec/AbstractInt.ts @@ -41,8 +41,14 @@ export default abstract class AbstractInt extends BN implements Codec { if (isHex(value)) { return hexToBn(value, { isLe: true, isNegative }).toString(); } else if (isU8a(value)) { - // NOTE When passing u8a in (typically from decoded data), it is always LE - return u8aToBn(value.subarray(0, bitLength / 8), { isLe: true, isNegative }).toString(); + try { + // NOTE When passing u8a in (typically from decoded data), it is always LE + return u8aToBn(value.subarray(0, bitLength / 8), { isLe: true, isNegative }).toString(); + } catch (error) { + console.error(`AbstractInt value decoding failed ${error.message}`, value); + + return '0'; + } } else if (isString(value)) { return new BN(value, 10).toString(); } diff --git a/packages/types/src/codec/EnumType.ts b/packages/types/src/codec/EnumType.ts index ea791b28a4..a2bdf987cf 100644 --- a/packages/types/src/codec/EnumType.ts +++ b/packages/types/src/codec/EnumType.ts @@ -84,7 +84,7 @@ export default class EnumType extends Base implements Codec { return EnumType.createValue(def, index, value); } - private static createValue (def: TypesDef, index: number, value?: any): Decoded { + private static createValue (def: TypesDef, index: number = 0, value?: any): Decoded { return { index, value: new (Object.values(def)[index])(value) diff --git a/packages/types/src/codec/U8a.ts b/packages/types/src/codec/U8a.ts index 632563768c..38d1b9a605 100644 --- a/packages/types/src/codec/U8a.ts +++ b/packages/types/src/codec/U8a.ts @@ -16,13 +16,13 @@ import { AnyU8a, Codec } from '../types'; * @noInheritDoc */ export default class U8a extends Uint8Array implements Codec { - constructor (value: AnyU8a) { + constructor (value?: AnyU8a) { super( U8a.decodeU8a(value) ); } - private static decodeU8a (value: any): Uint8Array { + private static decodeU8a (value?: any): Uint8Array { if (isU8a(value)) { return value; } diff --git a/packages/types/src/index.spec.ts b/packages/types/src/index.spec.ts new file mode 100644 index 0000000000..81dc0dba73 --- /dev/null +++ b/packages/types/src/index.spec.ts @@ -0,0 +1,45 @@ +// 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 { Constructor } from './types'; + +import extrinsics from '@polkadot/extrinsics/static'; + +import * as _Types from './index'; + +const Types = _Types as { [index: string]: Constructor }; + +describe('types', () => { + describe('default creation', () => { + Object.keys(Types).forEach((name) => { + it(`creates an empty ${name}`, () => { + const constructFn = () => + new Types[name](); + + if (name === 'Origin') { + expect(constructFn).toThrow(); + } else { + expect(constructFn).not.toThrow(); + } + }); + }); + }); + + describe('default creation (empty Bytes)', () => { + (Types.Method as any).injectMethods(extrinsics); + + Object.keys(Types).forEach((name) => { + it(`creates an empty ${name} (from empty bytes)`, () => { + const constructFn = () => + new Types[name](new Types.Bytes()); + + if (name === 'Origin') { + expect(constructFn).toThrow(); + } else { + expect(constructFn).not.toThrow(); + } + }); + }); + }); +});