Decode runtime metadata (#162)

* WIP

* Some output, still borken...

* Events decoded correctly

* WIP, metadata decoded

* String cleanups

* Expand with generics

* Add getters

* Start docs...

* Additional docs

* Extra whitespace on Compact

* CodecLength FIXME

* Remove FIXME (impl. takes care)

* Swap in LengthCompact encoding

* Extend docs, play with auto-type for struct

* Trim strings, add Type for type-specific String

* More string-type hackery

* Don't allow Type re-encoding

* Remove struct experiment

* Cleanup Option

* Remove extra type defs from CodecStruct usage

* Expand Storage function with map (and type returns)

* Description around BaseCodec

* Cleanup linting, add Metadata test

* Move to api-codec

* Extract uniq types

* Uniq updates

* Unwrap, ungeneric
This commit is contained in:
Jaco Greeff
2018-09-24 13:51:20 +02:00
committed by GitHub
parent baaf394ccc
commit d9b05cf9f2
30 changed files with 3604 additions and 38 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ const config = require('@polkadot/dev/config/jest');
module.exports = Object.assign({}, config, {
moduleNameMapper: {
'@polkadot/api-(format|jsonrpc|provider|rx)(.*)$': '<rootDir>/packages/api-$1/src/$2',
'@polkadot/api-(codec|format|jsonrpc|provider|rx)(.*)$': '<rootDir>/packages/api-$1/src/$2',
'@polkadot/api(.*)$': '<rootDir>/packages/api/src/$1',
'@polkadot/extrinsics(.*)$': '<rootDir>/packages/type-extrinsics/src/$1',
'@polkadot/jsonrpc(.*)$': '<rootDir>/packages/type-jsonrpc/src/$1',
View File
+15
View File
@@ -0,0 +1,15 @@
ISC License (ISC)
Copyright 2017-2018 @polkadot/api-codec authors & contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
+3
View File
@@ -0,0 +1,3 @@
# @polkadot/api-codec
Implementation of the parity-codec
+35
View File
@@ -0,0 +1,35 @@
{
"name": "@polkadot/api-codec",
"version": "0.29.7",
"description": "Implementation of the Parity codec",
"main": "index.js",
"keywords": [
"Polkadot",
"JsonRPC"
],
"author": "Jaco Greeff <jacogr@gmail.com>",
"maintainers": [
"Jaco Greeff <jacogr@gmail.com>"
],
"contributors": [],
"license": "ISC",
"engines": {
"node": ">=6.4"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
},
"repository": {
"type": "git",
"url": "git+https://github.com/polkadot-js/api.git"
},
"bugs": {
"url": "https://github.com/polkadot-js/api/issues"
},
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/api-codec#readme",
"dependencies": {
"@babel/runtime": "^7.0.0",
"@polkadot/util": "^0.30.4"
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import Metadata from './Metadata';
import rpcdata from './Metadata.rpc.json';
describe('Metadata', () => {
it('decodes properly', () => {
const decoded = new Metadata().fromU8a(
Uint8Array.from(rpcdata.result)
);
const str = JSON.stringify(decoded.toJSON());
console.error(str);
console.error(decoded.getUniqTypes());
expect(str).not.toBe(null);
});
});
+333
View File
@@ -0,0 +1,333 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import CodecArray from './base/Array';
import CodecBase from './base/Base';
import CodecEnum from './base/Enum';
import CodecEnumType from './base/EnumType';
import CodecOption from './base/Option';
import CodecStruct from './base/Struct';
import String from './String';
import Type from './Type';
import U16 from './U16';
// Decodes the runtime metadata as passed through from the `state_getMetadata` call. This
// file is probably best understood from the bottom-up, i.e. start reading right at the
// end and work up. (Just so we don't use before definition)
class EventMetadata extends CodecStruct {
constructor () {
super({
name: String,
arguments: CodecArray.with(Type),
documentation: CodecArray.with(String)
});
}
get arguments (): CodecArray<Type> {
return this.raw.arguments as CodecArray<Type>;
}
get documentation (): CodecArray<String> {
return this.raw.documentation as CodecArray<String>;
}
get name (): String {
return this.raw.name as String;
}
}
class OuterEventMetadataEvent extends CodecStruct {
constructor () {
super({
name: String,
events: CodecArray.with(EventMetadata)
});
}
get events (): CodecArray<EventMetadata> {
return this.raw.events as CodecArray<EventMetadata>;
}
get name (): String {
return this.raw.name as String;
}
}
class OuterEventMetadata extends CodecStruct {
constructor () {
super({
name: String,
events: CodecArray.with(OuterEventMetadataEvent)
});
}
get events (): CodecArray<OuterEventMetadataEvent> {
return this.raw.events as CodecArray<OuterEventMetadataEvent>;
}
get name (): String {
return this.raw.name as String;
}
}
class FunctionArgumentMetadata extends CodecStruct {
constructor () {
super({
name: String,
type: Type
});
}
get name (): String {
return this.raw.name as String;
}
get type (): Type {
return this.raw.type as Type;
}
}
class FunctionMetadata extends CodecStruct {
constructor () {
super({
id: U16,
name: String,
arguments: CodecArray.with(FunctionArgumentMetadata),
documentation: CodecArray.with(String)
});
}
get arguments (): CodecArray<FunctionArgumentMetadata> {
return this.raw.arguments as CodecArray<FunctionArgumentMetadata>;
}
get documentation (): CodecArray<String> {
return this.raw.documentation as CodecArray<String>;
}
get id (): U16 {
return this.raw.id as U16;
}
get name (): String {
return this.raw.name as String;
}
}
class CallMetadata extends CodecStruct {
constructor () {
super({
name: String,
functions: CodecArray.with(FunctionMetadata)
});
}
get functions (): CodecArray<FunctionMetadata> {
return this.raw.functions as CodecArray<FunctionMetadata>;
}
get name (): String {
return this.raw.name as String;
}
}
class ModuleMetadata extends CodecStruct {
constructor () {
super({
name: String,
call: CallMetadata
});
}
get call (): CallMetadata {
return this.raw.call as CallMetadata;
}
get name (): String {
return this.raw.name as String;
}
}
class StorageFunctionModifier extends CodecEnum {
constructor () {
super(['None', 'Default', 'Required']);
}
}
class StorageFunctionType$Map extends CodecStruct {
constructor () {
super({
key: Type,
value: Type
});
}
get key (): Type {
return this.raw.type as Type;
}
get value (): Type {
return this.raw.value as Type;
}
}
class StorageFunctionType extends CodecEnumType<Type | StorageFunctionType$Map> {
constructor () {
super([
Type,
StorageFunctionType$Map
], ['Plain', 'Map']);
}
get isMap (): boolean {
return this.toNumber() === 1;
}
get asMap (): StorageFunctionType$Map {
return (this.raw as CodecBase<StorageFunctionType$Map>).raw;
}
get asType (): Type {
return (this.raw as CodecBase<Type>).raw;
}
}
class StorageFunctionMetadata extends CodecStruct {
constructor () {
super({
name: String,
modifier: StorageFunctionModifier,
type: StorageFunctionType,
documentation: CodecArray.with(String)
});
}
get documentation (): CodecArray<String> {
return this.raw.documentation as CodecArray<String>;
}
get name (): String {
return this.raw.name as String;
}
get modifier (): StorageFunctionModifier {
return this.raw.modifier as StorageFunctionModifier;
}
get type (): StorageFunctionType {
return this.raw.type as StorageFunctionType;
}
}
class StorageMetadata extends CodecStruct {
constructor () {
super({
prefix: String,
functions: CodecArray.with(StorageFunctionMetadata)
});
}
get functions (): CodecArray<StorageFunctionMetadata> {
return this.raw.functions as CodecArray<StorageFunctionMetadata>;
}
get prefix (): String {
return this.raw.prefix as String;
}
}
class RuntimeModuleMetadata extends CodecStruct {
constructor () {
super({
prefix: String,
module: ModuleMetadata,
storage: CodecOption.with(StorageMetadata)
});
}
get module (): ModuleMetadata {
return this.raw.module as ModuleMetadata;
}
get prefix (): String {
return this.raw.prefix as String;
}
get storage (): StorageMetadata | undefined {
return (this.raw.storage as CodecOption<StorageMetadata>).value;
}
}
export default class RuntimeMetadata extends CodecStruct {
constructor (value?: any) {
super({
outerEvent: OuterEventMetadata,
modules: CodecArray.with(RuntimeModuleMetadata)
}, value);
}
// FIXME Really not crazy about having to manually add all the getters. Preferably it should
// be done automagically in the actual CodecStruct - however what is really important here
// here is that we should nbot lose the autocompletion and checking that TS gives us. So if
// we have to choose between the 2, manual defs it would have to be.
get events (): CodecArray<OuterEventMetadataEvent> {
return (this.raw.outerEvent as OuterEventMetadata).events;
}
get modules (): CodecArray<RuntimeModuleMetadata> {
return this.raw.modules as CodecArray<RuntimeModuleMetadata>;
}
// Helper to retrieve a list of all type that are found, sorted and de-deuplicated
getUniqTypes (): Array<string> {
// Quick and dirty flatten (.flat() not available)
const flatten = (list: Array<any>): Array<any> =>
list.reduce((result, entry) => {
return result.concat(
Array.isArray(entry)
? flatten(entry)
: entry
);
}, []);
// Quick and dirty uniq
const uniq = (list: Array<any>): Array<any> =>
list.reduce((result, entry) => {
if (!result.includes(entry)) {
result.push(entry);
}
return result;
}, [] as Array<any>);
const events = this.events.map((module) =>
module.events.map((event) =>
event.arguments.map((argument) =>
argument.raw
)
)
);
const storages = this.modules.map((module) =>
module.storage
? module.storage.functions.map((fn) =>
fn.type.isMap
? [fn.type.asMap.key.raw, fn.type.asMap.value.raw]
: [fn.type.asType.raw]
)
: []
);
const args = this.modules.map((module) =>
module.module.call.functions.map((fn) =>
fn.arguments.map((argument) =>
argument.type.raw
)
)
);
return uniq(
flatten([events, storages, args]).filter((value) => value)
).sort();
}
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import u8aFromUtf8 from '@polkadot/util/u8a/fromUtf8';
import u8aToUtf8 from '@polkadot/util/u8a/toUtf8';
import u8aConcat from '@polkadot/util/u8a/concat';
import CodecBase from './base/Base';
import Length from './base/LengthCompact';
// This is a string wrapper, along with the length. It is used both for strings as well
// as stuff like documentation.
//
// TODO
// - Strings should probably be trimmed (docs do come through with extra padding)
// - Potentially we want a "TypeString" extension to this. Basically something that
// wraps the `Balance`, `T::AccountId`, etc. The reasoning - with a "TypeString"
// we can nicely strip types down like "T::AcountId" -> "AccountId"
export default class String extends CodecBase<string> {
protected _length: Length;
constructor (value: string = '') {
super(value);
this._length = new Length(value.length);
}
get length (): number {
return this.raw.length;
}
byteLength (): number {
// NOTE String gets trimmed in the fromU8a, so get the original length from the length
// placeholder and the associtated byteLength from that encoding
return this._length.toNumber() +
this._length.byteLength();
}
fromJSON (input: any): String {
throw new Error('String::fromJSON: unimplemented');
}
fromU8a (input: Uint8Array): String {
this._length.fromU8a(input);
const length = this._length.toNumber();
const offset = this._length.byteLength();
this.raw = u8aToUtf8(input.subarray(offset, offset + length)).trim();
return this;
}
toJSON (): any {
return this.toString();
}
toString (): string {
return this.raw;
}
toU8a (): Uint8Array {
// NOTE Since we trim in the fromU8a, here we re-calculate the actual length
const encoded = u8aFromUtf8(this.raw);
this._length.setValue(encoded.length);
return u8aConcat(
this._length.toU8a(),
encoded
);
}
}
+96
View File
@@ -0,0 +1,96 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import String from './String';
// given a starting index, find the closing >
function findClosing (value: string, start: number): number {
let depth = 0;
for (let index = start; index < value.length; index++) {
if (value[index] === '>') {
if (!depth) {
return index;
} else {
depth--;
}
} else if (value[index] === '<') {
depth++;
}
}
throw new Error(`Unable to find closing matching <> on '${value}' (start ${start})`);
}
function ungeneric (value: string): string {
for (let index = 0; index < value.length; index++) {
if (value[index] === '<') {
if (value.substr(index - 3, 3) !== 'Vec') {
const start = index + 1;
const end = findClosing(value, start);
value = `${value.substr(0, index)}${value.substr(end + 1)}`;
}
}
}
return value;
}
// remove the type traits
function untrait (value: string): string {
return value
// anything `T::<type>` to end up as `<type>`
.replace(/T::/g, '')
// `system::` with `` - basically we find `<T as system::Trait>`
.replace(/system::/g, '')
// replace `<T as Trait>::` (possibly sanitiused just above)
.replace(/<T as Trait>::/g, '');
}
// remove wrapping values, i.e. Box<Proposal> -> Proposal
function unwrap (check: string, value: string): string {
let index = 0;
while (index !== -1) {
index = value.indexOf(check);
if (index !== -1) {
const start = index + check.length;
const end = findClosing(value, start);
value = `${value.substr(start, end - start)}`;
}
}
return value;
}
// This is a extended version of String, specifically to handle types. Here we rely full on
// what string provides us, however we also "tweak" the types received from the runtime, i.e.
// we remove the `T::` prefixes found in some types for consistency accross implementation.
export default class Type extends String {
fromU8a (input: Uint8Array): String {
super.fromU8a(input);
// Hack around the Rust types to make them consistent for actual use
let result = untrait(this.raw);
// `Box<Proposal>` -> `Proposal`
result = unwrap('Box<', result);
// `MisbehaviorReport<Hash, BlockNumber>` -> `MisbehaviorReport`
result = ungeneric(result);
this.raw = result;
return this;
}
toU8a (): Uint8Array {
// Note Since we are mangling what we get in beyond recognition, we really should
// not allow the re-encoding. Additionally, this is probably more of a decoder-only
// helper, so treat it as such.
throw new Error('Type::toU8a: unimplemented');
}
}
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import BN from 'bn.js';
import CodecNumber from './base/Number';
export default class U16 extends CodecNumber {
constructor (value?: BN | number) {
super(value, 16);
}
}
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import u8aConcat from '@polkadot/util/u8a/concat';
import CodecBase from './Base';
import Length from './LengthCompact';
// This manages codec arrays. Intrernally it keeps track of the length (as decoded) and allows
// construction with the passed `Type` in the constructor. It aims to be an array-like structure,
// i.e. while it wraps an array, it provides a `length` property to get the actual length, `at(index)`
// to retrieve a specific item. Additionally the helper functions `map`, `filter`, `forEach` and
// `reduce` is exposed on the interface.
export default class CodecArray <
T extends CodecBase
> extends CodecBase<Array<T>> {
private _length: Length;
private _Type: { new(value?: any): T };
constructor (Type: { new(value?: any): T }, value: Array<any> = [] as Array<any>) {
super(
value.map((entry) =>
new Type(entry)
)
);
this._length = new Length(value.length);
this._Type = Type;
}
static with <O extends CodecBase> (Type: { new(value?: any): O }): { new(value?: any): CodecArray<O> } {
return class extends CodecArray<O> {
constructor (value?: Array<any>) {
super(Type, value);
}
};
}
byteLength (): number {
return this.raw.reduce((total, raw) => {
return total + raw.byteLength();
}, this._length.byteLength());
}
get length (): number {
return this.raw.length;
}
at (index: number): T {
return this.raw[index];
}
filter (fn: (entry: T, index?: number) => any): Array<T> {
return this.raw.filter(fn);
}
forEach (fn: (entry: T, index?: number) => any): any {
return this.raw.forEach(fn);
}
fromJSON (input: any): CodecArray<T> {
this.raw = input.map((input: any) =>
new this._Type().fromJSON(input)
);
return this;
}
fromU8a (input: Uint8Array): CodecArray<T> {
this._length.fromU8a(input);
const length = this._length.toNumber();
let offset = this._length.byteLength();
this.raw = [];
for (let index = 0; index < length; index++) {
const raw = new this._Type().fromU8a(input.subarray(offset));
this.raw.push(raw as T);
offset += raw.byteLength();
}
return this;
}
map <O> (fn: (entry: T, index?: number) => O): Array<O> {
return this.raw.map(fn);
}
reduce <O> (fn: (result: O, entry: T, index?: number) => O, initial: O): O {
return this.raw.reduce(fn, initial);
}
toJSON (): any {
return this.raw.map((entry) =>
entry.toJSON()
);
}
toU8a (): Uint8Array {
return u8aConcat(
this._length.toU8a(),
...this.raw.map((entry) =>
entry.toU8a()
)
);
}
toString (): string {
const data = this.raw.map((entry) =>
entry.toString()
).join(', ');
return `[${data}]`;
}
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// The actual base class for any type. As it stands, this should not be used on it's own,
// so we expose the interface, but throw on anything. This allows subclasses to actaully
// only implement part (things that are unused/unexpected should just error-out)
//
// TODO
// - This could probably be abstract, as long as we have the functions abstract as well
export default class CodecBase <T = any> {
raw: T;
constructor (value?: any) {
this.raw = value;
}
byteLength (): number {
throw new Error('CodecBase::byteLength: unimplemented');
}
fromJSON (input: any): CodecBase <T> {
throw new Error('CodecBase::fromJSON: unimplemented');
}
fromU8a (input: Uint8Array): CodecBase <T> {
throw new Error('CodecBase::fromU8a: unimplemented');
}
toJSON (): any {
throw new Error('CodecBase::toJSON: unimplemented');
}
toString (): string {
throw new Error('CodecBase::toString: unimplemented');
}
toU8a (): Uint8Array {
throw new Error('CodecBase::toU8a: unimplemented');
}
}
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import CodecBase from './Base';
// A codec wrapper for an enum. Enums are encoded as a single byte, where the byte
// is a zero-indexed value. This class allows you to retrieve the value either
// by `toNumber()` exposing the actual raw index, or `toString()` returning a
// string representation (as provided as part of the constructor)
//
// TODO:
// - It would be great if this could actually wrap actual TS enums
export default class CodecEnum extends CodecBase<number> {
private _strings: Array<string>;
constructor (strings: Array<string>, value: number = 0) {
super(value);
this._strings = strings;
}
byteLength (): number {
return 1;
}
fromJSON (input: any): CodecEnum {
this.raw = input;
return this;
}
fromU8a (input: Uint8Array): CodecEnum {
this.raw = input[0];
return this;
}
toJSON (): any {
return this.raw;
}
toU8a (): Uint8Array {
return new Uint8Array([this.raw]);
}
toNumber (): number {
return this.raw;
}
toString (): string {
return this._strings[this.raw];
}
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import CodecBase from './Base';
// This implements an enum, that based on the value wraps a different type. It is effectively an
// extension to enum where the value type is determined by the actual index.
//
// TODO:
// - As per Enum, actually use TS enum
// - It should rather probably extend Enum instead of copying code
// - There doesn't actually seem to be a way to get to the actual determined/wrapped value
export default class CodecEnumType <T> extends CodecBase<CodecBase<T>> {
private _Type: Array<{ new(value?: any): CodecBase }>;
private _index: number;
private _strings: Array<string>;
constructor (Type: Array<{ new(value?: any): CodecBase }>, strings: Array<string>, index: number = 0) {
super(
new Type[index]()
);
this._Type = Type;
this._index = index;
this._strings = strings;
}
byteLength (): number {
return 1 + this.raw.byteLength();
}
fromJSON (input: any): CodecEnumType<T> {
throw new Error('CodecEnumType:fromJSON: unimplemented');
}
fromU8a (input: Uint8Array): CodecEnumType<T> {
this._index = input[0];
this.raw = new this._Type[this._index]().fromU8a(input.subarray(1));
return this;
}
toJSON (): any {
return this.raw;
}
toU8a (): Uint8Array {
throw new Error('CodecEnumType:toU8a: unimplemented');
}
toNumber (): number {
return this._index;
}
toString (): string {
return this._strings[this._index];
}
}
@@ -0,0 +1,26 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import LengthCompact from './LengthCompact';
describe('Compact', () => {
let compact;
beforeEach(() => {
});
// FIXME skipped since this one does not actually work as expected :()
// basically, bring in the tests since encoding with lengths are still
// proplematic (decoding at this point does work)
it.skip('encodes short u8', () => {
compact = new LengthCompact(78);
expect(
compact.toU8a()
).toEqual(
new Uint8Array([78 << 2])
);
});
});
@@ -0,0 +1,80 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import BN from 'bn.js';
import bnToU8a from '@polkadot/util/bn/toU8a';
import u8aConcat from '@polkadot/util/u8a/concat';
import u8aToBn from '@polkadot/util/u8a/toBn';
import CodecBaseLength from './LengthFixed';
const MAX_VAL_U8 = new BN(2).pow(new BN(8 - 2)).subn(1);
const MAX_VAL_U16 = new BN(2).pow(new BN(16 - 2)).subn(1);
const MAX_VAL_U32 = new BN(2).pow(new BN(32 - 2)).subn(1);
// A new compact length-encoding algorithm. It performs the same function as Length, however
// differs in that it uses a variable number of bytes to do the actual encoding. From the Rust
// implementation for compact encoding
//
// 0b00 00 00 00 / 00 00 00 00 / 00 00 00 00 / 00 00 00 00
// (0 ... 2**6 - 1) (u8)
// xx xx xx 00
// (2**6 ... 2**14 - 1) (u8, u16) low LH high
// yL yL yL 01 / yH yH yH yL
// (2**14 ... 2**30 - 1) (u16, u32) low LMMH high
// zL zL zL 10 / zM zM zM zL / zM zM zM zM / zH zH zH zM
// (2**30 ... 2**536 - 1) (u32, u64, u128, U256, U512, U520) straight LE-encoded
// nn nn nn 11 [ / zz zz zz zz ]{4 + n}
//
// Note: we use *LOW BITS* of the LSB in LE encoding to encode the 2 bit key.
export default class CodecLengthCompact extends CodecBaseLength {
byteLength (): number {
return this.toU8a().length;
}
fromU8a (input: Uint8Array): CodecLengthCompact {
const flag = input[0] & 0b11;
if (flag === 0b00) {
this.raw = new BN(input[0]).shrn(2);
} else if (flag === 0b01) {
this.raw = u8aToBn(input.slice(0, 2), true).shrn(2);
} else if (flag === 0b10) {
this.raw = u8aToBn(input.slice(0, 4), true).shrn(2);
} else {
this.raw = u8aToBn(input.subarray(1, 5), true);
}
return this;
}
toU8a (): Uint8Array {
if (this.raw.lte(MAX_VAL_U8)) {
return new Uint8Array([this.raw.toNumber() << 2]);
} else if (this.raw.lte(MAX_VAL_U16)) {
const u8a = bnToU8a(this.raw, 16, true);
u8a.set([
(u8a[0] << 2) | 0b01
]);
return u8a;
} else if (this.raw.lte(MAX_VAL_U32)) {
const u8a = bnToU8a(this.raw, 32, true);
u8a.set([
(u8a[0] << 2) | 0b10
]);
return u8a;
}
return u8aConcat(
new Uint8Array([
0b11
]),
bnToU8a(this.raw, 32, true)
);
}
}
@@ -0,0 +1,61 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import BN from 'bn.js';
import bnToBn from '@polkadot/util/bn/toBn';
import bnToU8a from '@polkadot/util/bn/toU8a';
import u8aToBn from '@polkadot/util/u8a/toBn';
import CodecBase from './Base';
// A basic u32 LE length-encoder. It is basically used in Vectors, Arrays and String
// (anything variable) to do the encoding/decoding for the length prefixes. (It has
// been superceded in use by the compact encoder, i.e. LengthCompact)
export default class CodecLengthFixed extends CodecBase<BN> {
constructor (value: BN | number = new BN(0)) {
super(
bnToBn(value)
);
}
byteLength (): number {
return 4;
}
fromJSON (): CodecLengthFixed {
throw new Error('CodecLength::fromJSON: unimplemented');
}
fromNumber (value: BN | number): CodecLengthFixed {
this.raw = bnToBn(value);
return this;
}
fromU8a (input: Uint8Array): CodecLengthFixed {
this.raw = u8aToBn(input.subarray(0, 4), true);
return this;
}
toJSON (): any {
throw new Error('CodecLength::toJSON: unimplemented');
}
toNumber (): number {
return this.raw.toNumber();
}
toString (): string {
throw new Error('CodecLength::toString: unimplemented');
}
toU8a (): Uint8Array {
return bnToU8a(this.raw, 32, true);
}
setValue (value: BN | number): void {
this.raw = bnToBn(value);
}
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import BN from 'bn.js';
import isHex from '@polkadot/util/is/hex';
import bnToBn from '@polkadot/util/bn/toBn';
import bnToHex from '@polkadot/util/bn/toHex';
import bnToU8a from '@polkadot/util/bn/toU8a';
import hexToBn from '@polkadot/util/hex/toBn';
import u8aToBn from '@polkadot/util/u8a/toBn';
import CodecBase from './Base';
type BitLength = 8 | 16 | 32 | 64 | 128;
const DEFAULT_VALUE = new BN(0);
// A generic number codec. For Substrate all numbers are LE encoded, this handles the encoding
// and decoding of those numbers. Upon construction the bitLength is provided and any additional
// use keeps the number to this length.
//
// TODO:
// - Apart from encoding/decoding we don't actuall keep check on the sizes, is this good enough?
export default class CodecNumber extends CodecBase<BN> {
private _bitLength: BitLength;
constructor (value: BN | number = DEFAULT_VALUE, bitLength: BitLength = 64) {
super(
bnToBn(value)
);
this._bitLength = bitLength;
}
byteLength (): number {
return this._bitLength / 8;
}
fromJSON (input: any): CodecNumber {
this.raw = isHex(input)
? hexToBn(input)
: new BN(input);
return this;
}
fromU8a (input: Uint8Array): CodecNumber {
this.raw = u8aToBn(input.subarray(0, this.byteLength()), true);
return this;
}
toJSON (): any {
return this.toString();
}
toU8a (): Uint8Array {
return bnToU8a(this.raw, this._bitLength, true);
}
toString (): string {
return bnToHex(this.raw, this._bitLength);
}
toBn (): BN {
return this.raw;
}
toNumber (): number {
return this.raw.toNumber();
}
}
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import isUndefined from '@polkadot/util/is/undefined';
import CodecBase from './Base';
// An Option is an optional field. Basically the first byte indicates that there is
// is value to follow. If the byte is `1` there is an actual value. So the CodecOption
// implements that - decodes, checks for optionality and wraps the required structure
// with a value if/as required/found.
export default class CodecOption <T> extends CodecBase<CodecBase<T>> {
private _hasValue: boolean;
constructor (Value: { new(value?: any): CodecBase<T> }, value?: any) {
super(
new Value(value)
);
this._hasValue = !isUndefined(value);
}
static with <O> (Type: { new(value?: any): CodecBase<O> }): { new(value?: any): CodecOption<O> } {
return class extends CodecOption<O> {
constructor (value?: any) {
super(Type, value);
}
};
}
get value (): T | undefined {
return this._hasValue
? this.raw.raw
: undefined;
}
byteLength (): number {
const childLength = this._hasValue
? this.raw.byteLength()
: 0;
return 1 + childLength;
}
fromJSON (input: any): CodecOption<T> {
this._hasValue = !isUndefined(input);
if (this._hasValue) {
this.raw.fromJSON(input);
}
return this;
}
fromU8a (input: Uint8Array): CodecOption<T> {
this._hasValue = input[0] === 1;
if (this._hasValue) {
this.raw.fromU8a(input.subarray(1));
}
return this;
}
toJSON (): any {
return this._hasValue
? this.raw.toJSON()
: undefined;
}
toU8a (): Uint8Array {
const u8a = new Uint8Array(this.byteLength());
if (this._hasValue) {
u8a.set([1]);
u8a.set(this.raw.toU8a(), 1);
}
return u8a;
}
toString (): string {
return this._hasValue
? this.raw.toString()
: '';
}
}
+99
View File
@@ -0,0 +1,99 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import u8aConcat from '@polkadot/util/u8a/concat';
import CodecBase from './Base';
// A Struct defines an Object with key/values - where the values are CodecCodecBase values. It removes
// a lot of repetition from the actual coding, define a structure type, pass it the key/CodecBase<T>
// values in the constructor and it manages the decoding. It is important that the constructor
// values matches 100% to the order in th Rust code, i.e. don't go crazy and make it alphabetical,
// it needs to decoded in the specific defined order.
//
// TODO:
// - Check the constructor, something is really, really wrong with the way the defs are used
export default class CodecStruct <
S = { [index: string]: { new(value?: any): CodecBase } },
T = { [K in keyof S]: CodecBase },
V = { [K in keyof S]: any }
> extends CodecBase<T> {
constructor (Struct: S, value: V = {} as V) {
super(
Object.keys(Struct).reduce((raw: T, key) => {
// @ts-ignore Ok, something weid is going on here or I just don't get it... it works,
// so ignore the checker, although it drives me batty. (It started when the [key in keyof T]
// was added, the idea is to provide better checks, which does backfire here, but works
// externally.)
raw[key] = new Struct[key](value[key]);
return raw;
}, {} as T)
);
}
static with <
S = { [index: string]: { new(value?: any): CodecBase } }
> (Struct: S): { new(value?: any): CodecStruct<S> } {
return class extends CodecStruct<S> {
constructor (value?: any) {
super(Struct, value);
}
};
}
byteLength (): number {
return Object.values(this.raw).reduce((length, entry) => {
return length += entry.byteLength();
}, 0);
}
fromJSON (input: any): CodecStruct<S, T, V> {
Object.keys(this.raw).forEach((key) => {
// @ts-ignore as above...
this.raw[key].fromJSON(input[key]);
});
return this;
}
fromU8a (input: Uint8Array): CodecStruct<S, T, V> {
Object.keys(this.raw).reduce((offset, key) => {
// @ts-ignore as above...
this.raw[key].fromU8a(input.subarray(offset));
// @ts-ignore as above...
return offset + this.raw[key].byteLength();
}, 0);
return this;
}
toJSON (): any {
return Object.keys(this.raw).reduce((json, key) => {
// @ts-ignore as above...
json[key] = this.raw[key].toJSON();
return json;
}, {} as any);
}
toU8a (): Uint8Array {
return u8aConcat(
...Object.keys(this.raw).map((key) =>
// @ts-ignore as above...
this.raw[key].toU8a()
)
);
}
toString (): string {
const data = Object.keys(this.raw).map((key) =>
// @ts-ignore as above...
`${key}: ${this.raw[key].toString()}`
).join(', ');
return `{${data}}`;
}
}
+1
View File
@@ -30,6 +30,7 @@
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/api-format#readme",
"dependencies": {
"@babel/runtime": "^7.0.0",
"@polkadot/api-codec": "^0.29.7",
"@polkadot/jsonrpc": "^0.29.7",
"@polkadot/primitives": "^0.29.7",
"@polkadot/util": "^0.30.4",
+23
View File
@@ -5,6 +5,7 @@
import { Param$Types } from '@polkadot/params/types';
import { FormatterFunction } from './types';
import Metadata from '@polkadot/api-codec/Metadata';
import addressEncode from '@polkadot/util-keyring/address/encode';
import bnDecode from '@polkadot/primitives/json/bn/decode';
import bytesDecode from '@polkadot/primitives/json/bytes/decode';
@@ -16,6 +17,27 @@ import isUndefined from '@polkadot/util/is/undefined';
import format from './format';
// NOTE Here we are trying to do things a bit differently, i.e. more in line with where we think things
// could go with the primitive classes. (Good testbed anyway for the decoding, although maybe a bit messy
// with duplication). Overall, bit hackly since it shoehorns in a potential new implementation into an
// original way of doing things
const metaDecode = (input: Array<number>) => {
const metadata = new Metadata();
try {
metadata.fromU8a(
Uint8Array.from(input)
);
} catch (error) {
console.error('current', JSON.stringify(metadata.toJSON()), error);
throw error;
}
return metadata;
};
const formatters = new Map<Param$Types, FormatterFunction>([
// publicKey -> address
['AccountId', addressEncode],
@@ -23,6 +45,7 @@ const formatters = new Map<Param$Types, FormatterFunction>([
['Bytes', bytesDecode],
['Hash', hashDecode],
['Header', headerDecode],
['MetaData', metaDecode],
['SignedBlock', blockDecode],
['u64', bnDecode]
]);
@@ -0,0 +1,62 @@
// Copyright 2017-2018 @polkadot/api-provider authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import Api from '../../api/src';
import Ws from '../src/ws';
describe.skip('e2e basics', () => {
let api;
beforeEach(() => {
jest.setTimeout(30000);
api = new Api(new Ws('ws://127.0.0.1:9944'));
});
it('subscribes to chain_newHead', (done) => {
let count = 0;
// tslint:disable-next-line
api.chain
.newHead((error, data) => {
if (error) {
return done(error);
}
expect(data).toBeDefined();
if (++count === 3) {
done();
}
})
.then((subscriptionId) => {
console.log('newHead: subscriptionId =', subscriptionId);
});
});
it('retrieves the pending extrinsics', () => {
return api.author
.pendingExtrinsics()
.then((extrinsics) => {
console.error('extrinsics', extrinsics);
})
.catch((error) => {
console.error(error);
throw error;
});
});
it('retrieves the wasm metadata', () => {
return api.state
.getMetadata()
.then((meta) => {
console.error(JSON.stringify(meta.toJSON()));
})
.catch((error) => {
console.error(error);
throw error;
});
});
});
@@ -7,7 +7,7 @@ import storage from '@polkadot/storage';
import Api from '../../api/src';
import Ws from '../src/ws';
describe.skip('e2e', () => {
describe.skip('e2e krumme lanke', () => {
let api;
beforeEach(() => {
@@ -15,27 +15,6 @@ describe.skip('e2e', () => {
api = new Api(new Ws('ws://127.0.0.1:9944'));
});
it('subscribes to chain_newHead', (done) => {
let count = 0;
// tslint:disable-next-line
api.chain
.newHead((error, data) => {
if (error) {
return done(error);
}
expect(data).toBeDefined();
if (++count === 3) {
done();
}
})
.then((subscriptionId) => {
console.log('newHead: subscriptionId =', subscriptionId);
});
});
it('subscribes to storage', (done) => {
api.state
.subscribeStorage(
@@ -85,17 +64,4 @@ describe.skip('e2e', () => {
throw error;
});
});
it('retrieves the pending extrinsics', () => {
return api.author
.pendingExtrinsics()
.then((extrinsics) => {
console.error('extrinsics', extrinsics);
})
.catch((error) => {
console.error(error);
throw error;
});
});
});
+18
View File
@@ -27,6 +27,20 @@ const callAt: CreateItemOptions = {
type: 'Bytes'
};
const getMetadata: CreateItemOptions = {
description: 'Returns the runtime metadata',
params: [],
type: 'MetaData'
};
const getMetadataAt: CreateItemOptions = {
description: 'Returns the runtime metadata',
params: [
param('block', 'Hash')
],
type: 'MetaData'
};
const getStorage: CreateItemOptions = {
description: 'Retrieves the storage for a key',
params: [
@@ -110,6 +124,10 @@ export default (name: Interface$Sections): Section<Interfaces, PrivateMethods, P
createMethod('call')(call),
callAt:
createMethod('callAt')(callAt),
getMetadata:
createMethod('getMetadata')(getMetadata),
getMetadataAt:
createMethod('getMetadataAt')(getMetadataAt),
getStorage:
createMethod('getStorage')(getStorage),
getStorageAt:
@@ -80,6 +80,10 @@ export default function decodeValue (decode: Decoder, type: Param$Type, _input:
case 'StorageKeyValue':
return keyValue(input);
// HACKY, but a stopgap...
case 'MetaData':
return u8a(input, (input as Uint8Array).length * 8, 0);
case 'MisbehaviorReport':
return misbehavior(input);
+5 -2
View File
@@ -14,7 +14,7 @@ export type EncodingVersions = 'poc-1' | 'latest';
// - @polkadot/storage/key/params.ts
// - decode/value/index.js
// - encode/type/index.js
export type Param$Type = 'AccountId' | 'AccountIndex' | 'Balance' | 'BlockNumber' | 'bool' | 'Bytes' | 'Call' | 'CandidateReceipt' | 'Code' | 'Digest' | 'Gas' | 'Hash' | 'Header' | 'KeyValue' | 'MisbehaviorReport' | 'ParachainId' | 'PrendingExtrinsics' | 'PropIndex' | 'Proposal' | 'ReferendumIndex' | 'SessionKey' | 'Signature' | 'SignedBlock' | 'StorageKey' | 'StorageKeyValue' | 'StorageResult' | 'StorageResultSet' | 'String' | 'Timestamp' | 'u32' | 'u64' | 'u128' | 'VoteIndex' | 'VoteThreshold';
export type Param$Type = 'AccountId' | 'AccountIndex' | 'Balance' | 'BlockNumber' | 'bool' | 'Bytes' | 'Call' | 'CandidateReceipt' | 'Code' | 'Digest' | 'Gas' | 'Hash' | 'Header' | 'KeyValue' | 'MetaData' | 'MisbehaviorReport' | 'ParachainId' | 'PrendingExtrinsics' | 'PropIndex' | 'Proposal' | 'ReferendumIndex' | 'SessionKey' | 'Signature' | 'SignedBlock' | 'StorageKey' | 'StorageKeyValue' | 'StorageResult' | 'StorageResultSet' | 'String' | 'Timestamp' | 'u32' | 'u64' | 'u128' | 'VoteIndex' | 'VoteThreshold';
export type Param$Type$Array = Array<Param$Type | Array<Param$Type | Array<Param$Type>>>;
@@ -48,12 +48,15 @@ export type BlockDecoded = {
justification: BlockJustificationDecoded
};
export type MetaData = {
};
export type KeyValue = {
key: Uint8Array,
value: Uint8Array
}
export type Param$Value = Digest | Header | KeyValue | MisbehaviorReport | ExtrinsicDecoded | BN | Date | Uint8Array | boolean | number | string | null;
export type Param$Value = Digest | Header | KeyValue | MetaData | MisbehaviorReport | ExtrinsicDecoded | BN | Date | Uint8Array | boolean | number | string | null;
export type Param$Value$Array = Array<Param$Value | Array<Param$Value | Array<Param$Value>>>;
+1
View File
@@ -5,6 +5,7 @@
"baseUrl": ".",
"paths":
{
"@polkadot/api-codec/*": ["packages/api-codec/src/*"],
"@polkadot/api-format/*": ["packages/api-format/src/*"],
"@polkadot/api-provider/*": ["packages/api-provider/src/*"],
"@polkadot/api-rx/*": ["packages/api-rx/src/*"],