Support and return default values (#547)

* Support and return default values

* e2e tests for queries, with and without defaults

* remove .only

* Remove unused Codec import

* Remove commented import

* Add default creation test

* Test creation with empty Bytes

* Default storage test

* Remove non-existent throw test
This commit is contained in:
Jaco Greeff
2019-01-09 16:04:56 +01:00
committed by GitHub
parent c17036b028
commit 6ddc9e408c
13 changed files with 153 additions and 59 deletions
+18 -1
View File
@@ -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);
+3 -1
View File
@@ -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())
)
);
+2 -2
View File
@@ -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
+9 -3
View File
@@ -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<EventData> } = {};
@@ -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 {
+29 -4
View File
@@ -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();
});
});
});
});
});
+5 -36
View File
@@ -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<Text> | Array<string>
};
export class StorageFunctionMetadata<T extends Codec = Codec> 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<T>
// : Bytes;
super({
name: Text,
modifier: StorageFunctionModifier,
@@ -215,36 +207,13 @@ export class StorageFunctionMetadata<T extends Codec = Codec> 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;
}
/**
-6
View File
@@ -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({
+8 -1
View File
@@ -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
+23
View File
@@ -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
*/
+8 -2
View File
@@ -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();
}
+1 -1
View File
@@ -84,7 +84,7 @@ export default class EnumType<T> extends Base<Codec> 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)
+2 -2
View File
@@ -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;
}
+45
View File
@@ -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();
}
});
});
});
});