Support new versioned metadata format (#632)

* Start something...

* Parsing via proxy (everything still all-ok)

* Imp[ort order (minor)

* WIP expanding the structure

* Adjust V1 structure

* Parsing seems to do the trick...

* Add CodeHash and Schedule types

* //v0/ -> /v0/

* Update Extrinsic.ts

* Updated with compact outerDispatch, non-throw construction

* v1 to V0 conversion

* Bubble non-magic errors in constructor

* Update e2e after testing them

* Bump version with CHANGELOG  (potentially breaking change)
This commit is contained in:
Jaco Greeff
2019-01-26 16:27:00 +01:00
committed by GitHub
parent f32404b1e4
commit fd93fbc4a6
55 changed files with 6081 additions and 240 deletions
+4
View File
@@ -1,3 +1,7 @@
# 0.41.1
- Support the V1 metadata specification from Substrate in addition to the currently testnet-active V0 version
# 0.40.1
- The API interfaces now require a WS-compatible provider, e.g. subscription support is a must. Previously the HTTPProvider could be use (although it was very limited in the interactions).
+1 -1
View File
@@ -9,5 +9,5 @@
"packages": [
"packages/*"
],
"version": "0.40.26"
"version": "0.41.0"
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/api-derive",
"version": "0.40.26",
"version": "0.41.0",
"description": "Common functions used across Polkadot, derived from RPC calls and storage queries.",
"main": "index.js",
"keywords": [
@@ -31,8 +31,8 @@
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/api-derive#readme",
"dependencies": {
"@babel/runtime": "^7.2.0",
"@polkadot/api": "^0.40.26",
"@polkadot/types": "^0.40.26",
"@polkadot/api": "^0.41.0",
"@polkadot/types": "^0.41.0",
"@types/memoizee": "^0.4.2",
"@types/rx": "^4.1.1",
"memoizee": "^0.4.14",
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/api",
"version": "0.40.26",
"version": "0.41.0",
"description": "An RxJS wrapper around the Polkadot JS RPC",
"main": "index.js",
"keywords": [
@@ -30,12 +30,12 @@
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/api#readme",
"dependencies": {
"@babel/runtime": "^7.2.0",
"@polkadot/api-derive": "^0.40.26",
"@polkadot/extrinsics": "^0.40.26",
"@polkadot/rpc-provider": "^0.40.26",
"@polkadot/rpc-rx": "^0.40.26",
"@polkadot/storage": "^0.40.26",
"@polkadot/types": "^0.40.26",
"@polkadot/api-derive": "^0.41.0",
"@polkadot/extrinsics": "^0.41.0",
"@polkadot/rpc-provider": "^0.41.0",
"@polkadot/rpc-rx": "^0.41.0",
"@polkadot/storage": "^0.41.0",
"@polkadot/types": "^0.41.0",
"@types/rx": "^4.1.1",
"rxjs": "^6.3.3"
}
+3 -3
View File
@@ -300,8 +300,8 @@ export default abstract class ApiBase<OnCall> implements ApiBaseInterface<OnCall
this._runtimeVersion = await this._rpcBase.chain.getRuntimeVersion();
this._genesisHash = await this._rpcBase.chain.getBlockHash(0);
const extrinsics = extrinsicsFromMeta(this.runtimeMetadata);
const storage = storageFromMeta(this.runtimeMetadata);
const extrinsics = extrinsicsFromMeta(this.runtimeMetadata.asV0);
const storage = storageFromMeta(this.runtimeMetadata.asV0);
this._extrinsics = this.decorateExtrinsics(extrinsics, this.onCall);
this._query = this.decorateStorage(storage, this.onCall);
@@ -312,7 +312,7 @@ export default abstract class ApiBase<OnCall> implements ApiBaseInterface<OnCall
this._rx.query = this.decorateStorage(storage, rxOnCall);
this._rx.derive = this.decorateDerive(this._rx as ApiInterface$Rx, rxOnCall);
Event.injectMetadata(this.runtimeMetadata);
Event.injectMetadata(this.runtimeMetadata.asV0);
Method.injectMethods(extrinsics);
return true;
@@ -54,7 +54,7 @@ describe.skip('e2e queries', () => {
it('subscribes to queries (default)', (done) => {
api.query.staking.validatorPreferences(keyring.ferdie.address(), (prefs) => {
expect(prefs.unstakeThreshold.toNumber()).toBe(12);
expect(prefs.unstakeThreshold.toNumber()).toBe(3);
done();
});
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/rpc-core",
"version": "0.40.26",
"version": "0.41.0",
"description": "A JavaScript wrapper for the Polkadot JsonRPC interface",
"main": "index.js",
"keywords": [
@@ -30,9 +30,9 @@
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/rpc-core#readme",
"dependencies": {
"@babel/runtime": "^7.2.0",
"@polkadot/jsonrpc": "^0.40.26",
"@polkadot/rpc-provider": "^0.40.26",
"@polkadot/types": "^0.40.26",
"@polkadot/jsonrpc": "^0.41.0",
"@polkadot/rpc-provider": "^0.41.0",
"@polkadot/types": "^0.41.0",
"@polkadot/util": "^0.33.36"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/rpc-provider",
"version": "0.40.26",
"version": "0.41.0",
"description": "Transport providers for the API",
"main": "index.js",
"keywords": [
@@ -31,7 +31,7 @@
"dependencies": {
"@babel/runtime": "^7.2.0",
"@polkadot/keyring": "^0.33.36",
"@polkadot/storage": "^0.40.26",
"@polkadot/storage": "^0.41.0",
"@polkadot/util": "^0.33.36",
"@polkadot/util-crypto": "^0.33.36",
"@types/nock": "^9.3.0",
+1 -1
View File
@@ -11,7 +11,7 @@ import interfaces from '@polkadot/jsonrpc/index';
import testKeyring from '@polkadot/keyring/testing';
import storage from '@polkadot/storage/static';
import { Codec } from '@polkadot/types/types';
import metadata from '@polkadot/types/Metadata/static';
import metadata from '@polkadot/types/Metadata/v0/static';
import { Header } from '@polkadot/types/index';
import { bnToU8a, logger, u8aToHex } from '@polkadot/util';
import { randomAsU8a } from '@polkadot/util-crypto';
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/rpc-rx",
"version": "0.40.26",
"version": "0.41.0",
"description": "An RxJs wrapper around the Polkadot JS API",
"main": "index.js",
"keywords": [
@@ -30,8 +30,8 @@
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/rpc-rx#readme",
"dependencies": {
"@babel/runtime": "^7.2.0",
"@polkadot/rpc-core": "^0.40.26",
"@polkadot/rpc-provider": "^0.40.26",
"@polkadot/rpc-core": "^0.41.0",
"@polkadot/rpc-provider": "^0.41.0",
"@types/rx": "^4.1.1",
"rxjs": "^6.3.3"
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/extrinsics",
"version": "0.40.26",
"version": "0.41.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/api.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@@ -11,7 +11,7 @@
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.2.0",
"@polkadot/types": "^0.40.26",
"@polkadot/types": "^0.41.0",
"@polkadot/util": "^0.33.36"
}
}
@@ -3,12 +3,12 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { Extrinsic, Metadata } from '@polkadot/types/index';
import json from '@polkadot/types/Metadata/static';
import json from '@polkadot/types/Metadata/v0/static';
import fromMetadata from './fromMetadata';
// Use the pre-generated metadata
const metadata = new Metadata(json);
const metadata = new Metadata(json).asV0;
const newExtrinsics = fromMetadata(metadata);
describe('fromMetadata', () => {
+3 -3
View File
@@ -2,9 +2,9 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { RuntimeModuleMetadata } from '@polkadot/types/Metadata/Modules';
import { RuntimeModuleMetadata } from '@polkadot/types/Metadata/v0/Modules';
import { Methods, ModulesWithMethods } from '@polkadot/types/Method';
import { Metadata } from '@polkadot/types/index';
import MetadataV0 from '@polkadot/types/Metadata/v0';
import { stringCamelCase } from '@polkadot/util';
@@ -18,7 +18,7 @@ import extrinsics from './index';
* @param extrinsics - An extrinsics object to be extended.
* @param metadata - The metadata to extend the storage object against.
*/
export default function fromMetadata (metadata: Metadata): ModulesWithMethods {
export default function fromMetadata (metadata: MetadataV0): ModulesWithMethods {
let indexCount = -1;
const findIndex = (prefix: string): number => {
+3 -3
View File
@@ -2,11 +2,11 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import metadataRpc from '@polkadot/types/Metadata/static';
import Metadata from '@polkadot/types/Metadata';
import { Metadata } from '@polkadot/types/index';
import metadataRpc from '@polkadot/types/Metadata/v0/static';
import fromMetadata from './fromMetadata';
export default fromMetadata(
new Metadata(metadataRpc)
new Metadata(metadataRpc).asV0
);
@@ -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 { FunctionMetadata } from '@polkadot/types/Metadata/Modules';
import { FunctionMetadata } from '@polkadot/types/Metadata/v0/Modules';
import { MethodFunction } from '@polkadot/types/Method';
import { Method } from '@polkadot/types/index';
import { assert } from '@polkadot/util';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/jsonrpc",
"version": "0.40.26",
"version": "0.41.0",
"description": "Method definitions for the Polkadot RPC layer",
"main": "index.js",
"engines": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/storage",
"version": "0.40.26",
"version": "0.41.0",
"description": "Generic wrapper wrapper around state storage",
"main": "index.js",
"engines": {
@@ -31,7 +31,7 @@
"dependencies": {
"@babel/runtime": "^7.2.0",
"@polkadot/keyring": "^0.33.36",
"@polkadot/types": "^0.40.26",
"@polkadot/types": "^0.41.0",
"@polkadot/util": "^0.33.36",
"@polkadot/util-crypto": "^0.33.36"
}
@@ -2,13 +2,13 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import Metadata from '@polkadot/types/Metadata';
import json from '@polkadot/types/Metadata/static';
import { Metadata } from '@polkadot/types/index';
import json from '@polkadot/types/Metadata/v0/static';
import fromMetadata from './fromMetadata';
// Use the pre-generated metadata
const metadata = new Metadata(json);
const metadata = new Metadata(json).asV0;
const newStorage = fromMetadata(metadata);
describe('fromMetadata', () => {
+2 -2
View File
@@ -4,7 +4,7 @@
import { ModuleStorage, Storage } from './types';
import Metadata from '@polkadot/types/Metadata';
import MetadataV0 from '@polkadot/types/Metadata/v0';
import { stringLowerFirst } from '@polkadot/util';
import createFunction from './utils/createFunction';
@@ -17,7 +17,7 @@ import storage from './index';
* @param storage - A storage object to be extended.
* @param metadata - The metadata to extend the storage object against.
*/
export default function fromMetadata (metadata: Metadata): Storage {
export default function fromMetadata (metadata: MetadataV0): Storage {
return metadata.modules.reduce((result, moduleMetadata) => {
if (moduleMetadata.storage.isNone) {
return result;
+3 -3
View File
@@ -2,11 +2,11 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import metadataRpc from '@polkadot/types/Metadata/static';
import Metadata from '@polkadot/types/Metadata';
import { Metadata } from '@polkadot/types/index';
import metadataRpc from '@polkadot/types/Metadata/v0/static';
import fromMetadata from './fromMetadata';
export default fromMetadata(
new Metadata(metadataRpc)
new Metadata(metadataRpc).asV0
);
+1 -1
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 { StorageFunctionMetadata, StorageFunctionModifier, StorageFunctionType } from '@polkadot/types/Metadata/Modules';
import { StorageFunctionMetadata, StorageFunctionModifier, StorageFunctionType } from '@polkadot/types/Metadata/v0/Modules';
import { StorageFunction } from '@polkadot/types/StorageKey';
import Text from '@polkadot/types/Text';
import Vector from '@polkadot/types/codec/Vector';
@@ -4,7 +4,7 @@
import Compact from '@polkadot/types/codec/Compact';
import { createType } from '@polkadot/types/codec';
import { StorageFunctionMetadata } from '@polkadot/types/Metadata/Modules';
import { StorageFunctionMetadata } from '@polkadot/types/Metadata/v0/Modules';
import { StorageFunction } from '@polkadot/types/StorageKey';
import { Text } from '@polkadot/types/index';
import { assert, isNull, isUndefined, stringLowerFirst, stringToU8a, u8aConcat } from '@polkadot/util';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/types",
"version": "0.40.26",
"version": "0.41.0",
"description": "Implementation of the Parity codec",
"main": "index.js",
"keywords": [
+3 -3
View File
@@ -10,8 +10,8 @@ import Struct from './codec/Struct';
import Tuple from './codec/Tuple';
import U8aFixed from './codec/U8aFixed';
import { TypeDef, getTypeClass, getTypeDef } from './codec/createType';
import Metadata from './Metadata';
import { EventMetadata } from './Metadata/Events';
import MetadataV0 from './Metadata/v0';
import { EventMetadata } from './Metadata/v0/Events';
import Null from './Null';
const EventTypes: { [index: string]: Constructor<EventData> } = {};
@@ -119,7 +119,7 @@ export default class Event extends Struct {
// This is called/injected by the API on init, allowing a snapshot of
// the available system events to be used in lookups
static injectMetadata (metadata: Metadata): void {
static injectMetadata (metadata: MetadataV0): void {
metadata.events.forEach((section, sectionIndex) => {
const sectionName = stringCamelCase(section.name.toString());
+2 -2
View File
@@ -10,12 +10,12 @@ import AccountId from './AccountId';
import Event from './Event';
import EventRecord from './EventRecord';
import Metadata from './Metadata';
import metadataRpc from './Metadata/static';
import metadataRpc from './Metadata/v0/static';
describe('EventRecord', () => {
beforeEach(() => {
Event.injectMetadata(
new Metadata(metadataRpc)
new Metadata(metadataRpc).asV0
);
});
+1 -1
View File
@@ -13,7 +13,7 @@ import Struct from './codec/Struct';
import Address from './Address';
import ExtrinsicSignature from './ExtrinsicSignature';
import Hash from './Hash';
import { FunctionMetadata } from './Metadata/Modules';
import { FunctionMetadata } from './Metadata/v0/Modules';
import Method from './Method';
type ExtrinsicValue = {
@@ -0,0 +1,15 @@
// 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 MagicNumber, { MAGIC_NUMBER } from './MagicNumber';
describe('MagicNumber', () => {
it('succeeds when the magic number matches', () => {
expect(() => new MagicNumber(MAGIC_NUMBER)).not.toThrow();
});
it('fails when the magic number missmatches', () => {
expect(() => new MagicNumber(0x12345)).toThrow(/MagicNumber/);
});
});
@@ -0,0 +1,17 @@
// 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 U32 from '../U32';
import { assert } from '@polkadot/util';
export const MAGIC_NUMBER = new U32(0x6174656d); // `meta`, reversed for LE encoding
export const MAGIC_ERROR = -61746;
export default class MagicNumber extends U32 {
constructor (value?: any) {
super(value);
assert(this.eq(MAGIC_NUMBER), `MagicNumber: expected ${MAGIC_NUMBER.toHex()}, found ${this.toHex()}`, MAGIC_ERROR);
}
}
+16 -7
View File
@@ -8,27 +8,27 @@ import createType from '../codec/createType';
import Method from '../Method';
import Metadata from './index';
import latestParsed from './latest.substrate.json';
import latestParsed from './v1/latest.substrate.v1.json';
import rpcData from './static';
describe('Metadata', () => {
const metadata = new Metadata(rpcData);
it('decodes latest properly', () => {
const metadata = new Metadata(rpcData);
const str = JSON.stringify(metadata.toJSON());
console.error(str);
console.error(metadata.getUniqTypes());
expect(metadata.events.length).not.toBe(0);
expect(metadata.version).toBe(1);
expect(metadata.asV1.modules.length).not.toBe(0);
expect(str).toEqual(JSON.stringify(latestParsed));
});
describe('storage with default values', () => {
const metadata = new Metadata(rpcData);
Method.injectMethods(extrinsicsFromMeta(metadata.asV0));
Method.injectMethods(extrinsicsFromMeta(metadata));
metadata.modules.forEach((mod) => {
metadata.asV1.modules.forEach((mod) => {
if (mod.storage.isNone) {
return;
}
@@ -42,4 +42,13 @@ describe('Metadata', () => {
});
});
});
it('converts v1 to V0', () => {
const v0 = metadata.asV0;
const str = JSON.stringify(metadata.toJSON());
console.error(str);
expect(metadata.getUniqTypes()).toEqual(v0.getUniqTypes());
});
});
@@ -0,0 +1,112 @@
// 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 { MetadataInterface } from './types';
import { assert, isUndefined } from '@polkadot/util';
import EnumType from '../codec/EnumType';
import Struct from '../codec/Struct';
import Null from '../Null';
import MetadataV0 from './v0';
import MetadataV1 from './v1';
import v1ToV0 from './v1/toV0';
import MagicNumber from './MagicNumber';
class MetadataEnum extends EnumType<Null | MetadataV1> {
constructor (value?: any) {
super({
MetadataV0, // once rolled-out, can replace this with Null
MetadataV1
}, value);
}
/**
* @description Returns the wrapped values as a V0 object
*/
get asV0 (): MetadataV0 {
return this.value as MetadataV0;
}
/**
* @description Returns the wrapped values as a V1 object
*/
get asV1 (): MetadataV1 {
return this.value as MetadataV1;
}
/**
* @description The version this metadata represents
*/
get version (): number {
return this.index;
}
}
/**
* @name MetadataVersioned
* @description
* The versioned runtime metadata as a decoded structure
*/
export default class MetadataVersioned extends Struct implements MetadataInterface {
private _convertedV0?: MetadataV0;
constructor (value?: any) {
super({
magicNumber: MagicNumber,
metadata: MetadataEnum
}, value);
}
/**
* @description
*/
get magicNumber (): MagicNumber {
return this.get('magicNumber') as MagicNumber;
}
/**
* @description the metadata wrapped
*/
private get metadata (): MetadataEnum {
return this.get('metadata') as MetadataEnum;
}
/**
* @description the metadata version this structure represents
*/
get version (): number {
return this.metadata.index;
}
/**
* @description Returns the wrapped metadata as a V0 object
*/
get asV0 (): MetadataV0 {
if (this.metadata.version === 0) {
return this.metadata.asV0;
}
assert(this.metadata.version === 1, `Cannot convert metadata from v${this.metadata.version} to v0`);
if (isUndefined(this._convertedV0)) {
this._convertedV0 = v1ToV0(this.metadata.asV1);
}
return this._convertedV0;
}
/**
* @description Returns the wrapped values as a V1 object
*/
get asV1 (): MetadataV1 {
assert(this.metadata.version === 1, `Cannot convert metadata from v${this.metadata.version} to v1`);
return this.metadata.asV1;
}
getUniqTypes (): Array<string> {
return (this.metadata.value as any as MetadataInterface).getUniqTypes();
}
}
+25 -143
View File
@@ -2,161 +2,43 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { assert, hexToU8a, isHex, isU8a, isUndefined } from '@polkadot/util';
import { isHex, hexToU8a, u8aConcat } from '@polkadot/util';
import { getTypeDef, TypeDef, TypeDefInfo } from '../codec/createType';
import Compact from '../codec/Compact';
import Struct from '../codec/Struct';
import Vector from '../codec/Vector';
import { OuterDispatchMetadata, OuterDispatchCall } from './Calls';
import { OuterEventMetadata, OuterEventMetadataEvent } from './Events';
import { RuntimeModuleMetadata } from './Modules';
// 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)
// Quick and dirty flatten (.flat() not available)
function flattenUniq (list: Array<any>): Array<any> {
const flat = list.reduce((result, entry) => {
return result.concat(
Array.isArray(entry)
? flattenUniq(entry)
: entry
);
}, []);
return [...new Set(flat)]
.filter((value: any) => value)
.sort();
}
import { MAGIC_ERROR, MAGIC_NUMBER } from './MagicNumber';
import MetadataVersioned from './MetadataVersioned';
/**
* @name Metadata
* @description
* The runtime metadata as a decoded structure
* The versioned runtime metadata as a decoded structure
*/
export default class Metadata extends Struct {
constructor (value?: any) {
super({
outerEvent: OuterEventMetadata,
modules: Vector.with(RuntimeModuleMetadata),
outerDispatch: OuterDispatchMetadata
}, Metadata.decodeMetadata(value));
export default class Metadata extends MetadataVersioned {
constructor (value?: Uint8Array | string) {
super(Metadata.decodeMetadata(value));
}
static decodeMetadata (value: string | Uint8Array | object): object | Uint8Array {
if (isHex(value)) {
// We receive this as an hex in the JSON output from the Node.
// Convert to u8a and use the U8a version to do the actual parsing.
return Metadata.decodeMetadata(hexToU8a(value));
} else if (isU8a(value)) {
// HACK 13 Oct 2018 - For current running BBQ nodes, Metadata is not properly
// encoded, it does not have a length prefix. For latest substrate master, it
// is properly encoded. Here we pull the prefix, check it agianst the length -
// if matches, then we have the length, otherwise we assume it is an older node
// and use the whole buffer
const [offset, length] = Compact.decodeU8a(value);
// first we try and parse using the versioned structure, if this does fail,
// we adjust with the magic number and a manual version and re-try. As soon as
// we remove support for V0, we will just do a new here
private static decodeMetadata (_value: Uint8Array | string = new Uint8Array()): MetadataVersioned {
const value = isHex(_value)
? hexToU8a(_value)
: _value;
return value.length === (offset + length.toNumber())
? value.subarray(offset)
: value;
try {
return new MetadataVersioned(value);
} catch (error) {
if (error.code !== MAGIC_ERROR) {
throw error;
}
}
// Decode as normal struct
return value;
}
/**
* @description Wrapped [[OuterDispatchCall]]
*/
get calls (): Vector<OuterDispatchCall> {
return (this.get('outerDispatch') as OuterDispatchMetadata).calls;
}
/**
* @description Wrapped [[OuterEventMetadataEvent]]
*/
get events (): Vector<OuterEventMetadataEvent> {
return (this.get('outerEvent') as OuterEventMetadata).events;
}
/**
* @description Wrapped [[RuntimeModuleMetadata]]
*/
get modules (): Vector<RuntimeModuleMetadata> {
return this.get('modules') as Vector<RuntimeModuleMetadata>;
}
private get argNames () {
return this.modules.map((module) =>
module.module.call.functions.map((fn) =>
fn.arguments.map((argument) => argument.type.toString())
return new MetadataVersioned(
u8aConcat(
MAGIC_NUMBER.toU8a(), // manually add the magic number
Uint8Array.from([0]), // add the version for the original
value // the actual data as retrieved
)
);
}
private get eventNames () {
return this.events.map((module) =>
module.events.map((event) =>
event.arguments.map((argument) => argument.toString())
)
);
}
private get storageNames () {
return this.modules.map((module) =>
module.storage.isSome
? module.storage.unwrap().functions.map((fn) =>
fn.type.isMap
? [fn.type.asMap.key.toString(), fn.type.asMap.value.toString()]
: [fn.type.asType.toString()]
)
: []
);
}
/**
* @description Helper to retrieve a list of all type that are found, sorted and de-deuplicated
*/
getUniqTypes (): Array<string> {
const types = flattenUniq([this.argNames, this.eventNames, this.storageNames]);
this.validateTypes(types);
return types;
}
private validateTypes (types: Array<string>): void {
const extractTypes = (types: Array<string>): Array<any> => {
return types.map((type) => {
const decoded = getTypeDef(type);
switch (decoded.info) {
case TypeDefInfo.Plain:
return decoded.type;
case TypeDefInfo.Compact:
case TypeDefInfo.Option:
case TypeDefInfo.Vector:
return extractTypes([(decoded.sub as TypeDef).type]);
case TypeDefInfo.Tuple:
return extractTypes(
(decoded.sub as Array<TypeDef>).map((sub) => sub.type)
);
default:
throw new Error('Unreachable');
}
});
};
const allTypes = require('../index');
const missing = flattenUniq(extractTypes(types)).filter((type) =>
isUndefined(allTypes[type])
);
assert(missing.length === 0, `Unknown types found, no types for ${missing}`);
}
}
File diff suppressed because one or more lines are too long
+7
View File
@@ -0,0 +1,7 @@
// 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.
export interface MetadataInterface {
getUniqTypes (): Array<string>;
}
@@ -0,0 +1,18 @@
// 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.
// Quick and dirty flatten (.flat() not available)
export default function flattenUniq (list: Array<any>): Array<any> {
const flat = list.reduce((result, entry) => {
return result.concat(
Array.isArray(entry)
? flattenUniq(entry)
: entry
);
}, []);
return [...new Set(flat)]
.filter((value: any) => value)
.sort();
}
@@ -0,0 +1,6 @@
// 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.
export { default as flattenUniq } from './flattenUniq';
export { default as validateTypes } from './validateTypes';
@@ -0,0 +1,41 @@
// 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 { assert, isUndefined } from '@polkadot/util';
import { getTypeDef, TypeDef, TypeDefInfo } from '../../codec/createType';
import flattenUniq from './flattenUniq';
export default function validateTypes (types: Array<string>): void {
const extractTypes = (types: Array<string>): Array<any> => {
return types.map((type) => {
const decoded = getTypeDef(type);
switch (decoded.info) {
case TypeDefInfo.Plain:
return decoded.type;
case TypeDefInfo.Compact:
case TypeDefInfo.Option:
case TypeDefInfo.Vector:
return extractTypes([(decoded.sub as TypeDef).type]);
case TypeDefInfo.Tuple:
return extractTypes(
(decoded.sub as Array<TypeDef>).map((sub) => sub.type)
);
default:
throw new Error('Unreachable');
}
});
};
const allTypes = require('../../index');
const missing = flattenUniq(extractTypes(types)).filter((type) =>
isUndefined(allTypes[type])
);
assert(missing.length === 0, `Unknown types found, no types for ${missing}`);
}
@@ -2,10 +2,10 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import Struct from '../codec/Struct';
import Vector from '../codec/Vector';
import Text from '../Text';
import U16 from '../U16';
import Struct from '../../codec/Struct';
import Vector from '../../codec/Vector';
import Text from '../../Text';
import U16 from '../../U16';
export class OuterDispatchCall extends Struct {
constructor (value?: any) {
@@ -2,11 +2,11 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import Struct from '../codec/Struct';
import Tuple from '../codec/Tuple';
import Vector from '../codec/Vector';
import Text from '../Text';
import Type from '../Type';
import Struct from '../../codec/Struct';
import Tuple from '../../codec/Tuple';
import Vector from '../../codec/Vector';
import Text from '../../Text';
import Type from '../../Type';
export class EventMetadata extends Struct {
constructor (value?: any) {
@@ -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 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 metadata = new Metadata(rpcData);
const str = JSON.stringify(metadata.toJSON());
console.error(str);
console.error(metadata.getUniqTypes());
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();
});
});
});
});
});
@@ -2,17 +2,17 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AnyNumber } from '../types';
import { AnyNumber } from '../../types';
import Bytes from '../Bytes';
import Enum from '../codec/Enum';
import EnumType from '../codec/EnumType';
import Option from '../codec/Option';
import Struct from '../codec/Struct';
import Vector from '../codec/Vector';
import Text from '../Text';
import Type from '../Type';
import U16 from '../U16';
import Bytes from '../../Bytes';
import Enum from '../../codec/Enum';
import EnumType from '../../codec/EnumType';
import Option from '../../codec/Option';
import Struct from '../../codec/Struct';
import Vector from '../../codec/Vector';
import Text from '../../Text';
import Type from '../../Type';
import U16 from '../../U16';
export class FunctionArgumentMetadata extends Struct {
constructor (value?: any) {
+116
View File
@@ -0,0 +1,116 @@
// 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 { MetadataInterface } from '../types';
import { hexToU8a, isHex, isU8a } from '@polkadot/util';
import Compact from '../../codec/Compact';
import Struct from '../../codec/Struct';
import Vector from '../../codec/Vector';
import { flattenUniq, validateTypes } from '../util';
import { OuterDispatchMetadata, OuterDispatchCall } from './Calls';
import { OuterEventMetadata, OuterEventMetadataEvent } from './Events';
import { RuntimeModuleMetadata } from './Modules';
// 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)
/**
* @name MetadataV0
* @description
* The runtime metadata as a decoded structure
*/
export default class MetadataV0 extends Struct implements MetadataInterface {
constructor (value?: any) {
super({
outerEvent: OuterEventMetadata,
modules: Vector.with(RuntimeModuleMetadata),
outerDispatch: OuterDispatchMetadata
}, MetadataV0.decodeMetadata(value));
}
static decodeMetadata (value: string | Uint8Array | object): object | Uint8Array {
if (isHex(value)) {
// We receive this as an hex in the JSON output from the Node.
// Convert to u8a and use the U8a version to do the actual parsing.
return MetadataV0.decodeMetadata(hexToU8a(value));
} else if (isU8a(value)) {
// HACK 13 Oct 2018 - For current running BBQ nodes, Metadata is not properly
// encoded, it does not have a length prefix. For latest substrate master, it
// is properly encoded. Here we pull the prefix, check it agianst the length -
// if matches, then we have the length, otherwise we assume it is an older node
// and use the whole buffer
const [offset, length] = Compact.decodeU8a(value);
return value.length === (offset + length.toNumber())
? value.subarray(offset)
: value;
}
// Decode as normal struct
return value;
}
/**
* @description Wrapped [[OuterDispatchCall]]
*/
get calls (): Vector<OuterDispatchCall> {
return (this.get('outerDispatch') as OuterDispatchMetadata).calls;
}
/**
* @description Wrapped [[OuterEventMetadataEvent]]
*/
get events (): Vector<OuterEventMetadataEvent> {
return (this.get('outerEvent') as OuterEventMetadata).events;
}
/**
* @description Wrapped [[RuntimeModuleMetadata]]
*/
get modules (): Vector<RuntimeModuleMetadata> {
return this.get('modules') as Vector<RuntimeModuleMetadata>;
}
private get argNames () {
return this.modules.map((module) =>
module.module.call.functions.map((fn) =>
fn.arguments.map((argument) => argument.type.toString())
)
);
}
private get eventNames () {
return this.events.map((module) =>
module.events.map((event) =>
event.arguments.map((argument) => argument.toString())
)
);
}
private get storageNames () {
return this.modules.map((module) =>
module.storage.isSome
? module.storage.unwrap().functions.map((fn) =>
fn.type.isMap
? [fn.type.asMap.key.toString(), fn.type.asMap.value.toString()]
: [fn.type.asType.toString()]
)
: []
);
}
/**
* @description Helper to retrieve a list of all type that are found, sorted and de-deuplicated
*/
getUniqTypes (): Array<string> {
const types = flattenUniq([this.argNames, this.eventNames, this.storageNames]);
validateTypes(types);
return types;
}
}
File diff suppressed because one or more lines are too long
+129
View File
@@ -0,0 +1,129 @@
// 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 { MetadataInterface } from '../types';
import Option from '../../codec/Option';
import Struct from '../../codec/Struct';
import Vector from '../../codec/Vector';
import Text from '../../Text';
import { flattenUniq, validateTypes } from '../util';
import { EventMetadata } from '../v0/Events';
import { CallMetadata, StorageMetadata } from '../v0/Modules';
import { OuterDispatchCall } from './outerDispatch';
class RuntimeModuleMetadata extends Struct {
constructor (value?: any) {
super({
name: Text,
prefix: Text,
storage: Option.with(StorageMetadata),
calls: CallMetadata,
outerDispatch: Option.with(OuterDispatchCall),
events: Vector.with(EventMetadata)
}, value);
}
/**
* @description the module call
*/
get calls (): CallMetadata {
return this.get('calls') as CallMetadata;
}
/**
* @description the module events
*/
get events (): Vector<EventMetadata> {
return this.get('events') as Vector<EventMetadata>;
}
/**
* @description the module name
*/
get name (): Text {
return this.get('name') as Text;
}
/**
* @description the outer dispatch
*/
get outerDispatch (): Option<OuterDispatchCall> {
return this.get('outerDispatch') as Option<OuterDispatchCall>;
}
/**
* @description the module name
*/
get prefix (): Text {
return this.get('prefix') as Text;
}
/**
* @description the associated module storage
*/
get storage (): Option<StorageMetadata> {
return this.get('storage') as Option<StorageMetadata>;
}
}
/**
* @name MetadataV1
* @description
* The runtime metadata as a decoded structure
*/
export default class MetadataV1 extends Struct implements MetadataInterface {
constructor (value?: any) {
super({
modules: Vector.with(RuntimeModuleMetadata)
}, value);
}
/**
* @description The associated modules for this structure
*/
get modules (): Vector<RuntimeModuleMetadata> {
return this.get('modules') as Vector<RuntimeModuleMetadata>;
}
private get callNames () {
return this.modules.map((module) =>
module.calls.functions.map((fn) =>
fn.arguments.map((argument) => argument.type.toString())
)
);
}
private get eventNames () {
return this.modules.map((module) =>
module.events.map((event) =>
event.arguments.map((argument) => argument.toString())
)
);
}
private get storageNames () {
return this.modules.map((module) =>
module.storage.isSome
? module.storage.unwrap().functions.map((fn) =>
fn.type.isMap
? [fn.type.asMap.key.toString(), fn.type.asMap.value.toString()]
: [fn.type.asType.toString()]
)
: []
);
}
/**
* @description Helper to retrieve a list of all type that are found, sorted and de-deuplicated
*/
getUniqTypes (): Array<string> {
const types = flattenUniq([this.callNames, this.eventNames, this.storageNames]);
validateTypes(types);
return types;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,30 @@
// 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 Struct from '../../codec/Struct';
import Text from '../../Text';
import U16 from '../../U16';
export class OuterDispatchCall extends Struct {
constructor (value?: any) {
super({
name: Text,
index: U16
}, value);
}
/**
* @description The [[U16]] index for the call
*/
get index (): U16 {
return this.get('index') as U16;
}
/**
* @description The name for the call
*/
get name (): Text {
return this.get('name') as Text;
}
}
File diff suppressed because one or more lines are too long
+63
View File
@@ -0,0 +1,63 @@
// 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 { OuterDispatchCall, OuterDispatchMetadata } from '../v0/Calls';
import { OuterEventMetadata, OuterEventMetadataEvent } from '../v0/Events';
import { RuntimeModuleMetadata, ModuleMetadata } from '../v0/Modules';
import MetadataV0 from '../v0';
import MetadataV1 from './index';
function modulesV0 (v1: MetadataV1): Array<RuntimeModuleMetadata> {
return v1.modules.map((mod) => {
return new RuntimeModuleMetadata({
prefix: mod.prefix,
module: new ModuleMetadata({
name: 'Module',
call: mod.calls
}),
storage: mod.storage.isNone
? null
: mod.storage.unwrap()
});
});
}
function outerDispatchV0 (v1: MetadataV1): OuterDispatchMetadata {
return new OuterDispatchMetadata({
name: 'Call',
calls: v1.modules
.filter((mod) => mod.outerDispatch.isSome)
.map((mod) => {
const dispatch = mod.outerDispatch.unwrap();
return new OuterDispatchCall({
name: dispatch.name,
prefix: mod.prefix,
index: dispatch.index
});
})
});
}
function outerEventV0 (v1: MetadataV1): OuterEventMetadata {
return new OuterEventMetadata({
name: 'Event',
events: v1.modules.map((mod) =>
new OuterEventMetadataEvent([mod.prefix, mod.events])
)
});
}
export default function toV0 (v1: MetadataV1): MetadataV0 {
const outerEvent = outerEventV0(v1);
const modules = modulesV0(v1);
const outerDispatch = outerDispatchV0(v1);
return new MetadataV0({
outerEvent,
modules,
outerDispatch
});
}
+1 -1
View File
@@ -9,7 +9,7 @@ import { assert, isHex, isObject, isU8a, hexToU8a } from '@polkadot/util';
import { getTypeDef, getTypeClass } from './codec/createType';
import Struct from './codec/Struct';
import U8aFixed from './codec/U8aFixed';
import { FunctionMetadata, FunctionArgumentMetadata } from './Metadata/Modules';
import { FunctionMetadata, FunctionArgumentMetadata } from './Metadata/v0/Modules';
interface ArgsDef {
[index: string]: Constructor;
+1 -1
View File
@@ -7,7 +7,7 @@ import { AnyU8a } from './types';
import { isFunction } from '@polkadot/util';
import Bytes from './Bytes';
import { StorageFunctionMetadata } from './Metadata/Modules';
import { StorageFunctionMetadata } from './Metadata/v0/Modules';
export interface StorageFunction {
(arg?: any): Uint8Array;
+7
View File
@@ -102,6 +102,13 @@ export default class EnumType<T> extends Base<Codec> implements Codec {
return 1 + this.raw.encodedLength;
}
/**
* @description The index of the metadata value
*/
get index (): number {
return this._index;
}
/**
* @description Checks if the Enum points to a [[Null]] type
*/
@@ -5,10 +5,10 @@
const fs = require('fs');
import Metadata from '../Metadata';
import rpcdata from '../Metadata/static';
import rpcdata from '../Metadata/v0/static';
function metadataStringifyAsJson () {
const decoded = new Metadata(rpcdata);
const decoded = new Metadata(rpcdata).asV0;
return JSON.stringify(decoded.toJSON(), null, 2);
}
+10 -9
View File
@@ -7,7 +7,8 @@ import { stringCamelCase, stringLowerFirst } from '@polkadot/util';
import interfaces from '../../../type-jsonrpc/src';
import Metadata from '../Metadata';
import rpcdata from '../Metadata/static';
import MetadataV0 from '../Metadata/v0';
import rpcdata from '../Metadata/v0/static';
import Method from '../Method';
const ANCHOR_TOP = `<a id='top' style='text-decoration: none;'>`;
@@ -22,7 +23,7 @@ function sectionLink (sectionName: string) {
return `- **[${stringCamelCase(sectionName)}](#${stringCamelCase(sectionName)})**\n\n`;
}
function generateSectionLinks (sectionName: string, metadata: Metadata) {
function generateSectionLinks (sectionName: string, metadata: MetadataV0) {
switch (sectionName) {
case 'events':
return metadata.events.map((i) => i).sort().map((data: any) =>
@@ -81,7 +82,7 @@ function sortExtrinsicsSectionMethods (a: any, b: any) {
return nameA.localeCompare(nameB);
}
function addEvents (metadata: Metadata) {
function addEvents (metadata: MetadataV0) {
const renderHeading = `## ${ANCHOR_TOP}Events${DESC_EVENTS}`;
const orderedSections = metadata.events.map((i) => i).sort();
const renderAnchors = generateSectionLinks('events', metadata);
@@ -108,7 +109,7 @@ function addEvents (metadata: Metadata) {
}, renderHeading + renderAnchors);
}
function addExtrinsics (metadata: Metadata) {
function addExtrinsics (metadata: MetadataV0) {
const renderHeading = `## ${ANCHOR_TOP}Extrinsics${DESC_EXTRINSICS}`;
const orderedSections = metadata.modules.map((i) => i).sort();
const renderAnchors = generateSectionLinks('extrinsics', metadata);
@@ -134,7 +135,7 @@ function addExtrinsics (metadata: Metadata) {
}, renderHeading + renderAnchors);
}
function addStorage (metadata: Metadata) {
function addStorage (metadata: MetadataV0) {
const renderHeading = `## ${ANCHOR_TOP}Storage${DESC_STORAGE}`;
const orderedSections = metadata.modules.sort();
const renderAnchors = generateSectionLinks('storage', metadata) + sectionLink('substrate');
@@ -179,22 +180,22 @@ function writeToRpcMd () {
writeFile('docs/METHODS_RPC.md', addRpc());
}
function writeToStorageMd (metadata: Metadata) {
function writeToStorageMd (metadata: MetadataV0) {
const options = { flags: 'r', encoding: 'utf8' };
const data = fs.readFileSync('packages/types/src/scripts/METHODS_STORAGE_SUBSTRATE.md', options);
writeFile('docs/METHODS_STORAGE.md', addStorage(metadata), data);
}
function writeToExtrinsicsMd (metadata: Metadata) {
function writeToExtrinsicsMd (metadata: MetadataV0) {
writeFile('docs/METHODS_EXTRINSICS.md', addExtrinsics(metadata));
}
function writeToEventsMd (metadata: Metadata) {
function writeToEventsMd (metadata: MetadataV0) {
writeFile('docs/METHODS_EVENTS.md', addEvents(metadata));
}
const metadata = new Metadata(rpcdata);
const metadata = new Metadata(rpcdata).asV0;
writeToRpcMd();
writeToStorageMd(metadata);