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:
+1
-1
@@ -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',
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,3 @@
|
||||
# @polkadot/api-codec
|
||||
|
||||
Implementation of the parity-codec
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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}]`;
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
: '';
|
||||
}
|
||||
}
|
||||
@@ -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}}`;
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-35
@@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
|
||||
Vendored
+5
-2
@@ -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>>>;
|
||||
|
||||
|
||||
@@ -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/*"],
|
||||
|
||||
Reference in New Issue
Block a user