Round out metadata types (#169)

* Fake Origin for compatibility

* Add Gas

* Add Moment

* Cleanups

* NewAccountOutcome

* Permill & Perbill

* PrendingExtrinsics -> PendingExtrinsics

* Extrinsics & PendingExtrinsics

* Alias String -> Text (as found in jsonrpc, expose it)

* MetaDate -> Metadata (compat)

* Add StorageData, StorageKey & ChangeSet

* Cleanup comments

* Comment update

* Comment typo

* Add ParachainId

* Add Tuple type

* Tuple tests

* Clenaup Tuple generics

* Expand comment

* Expose keys/values on struct

* Array-like at -> get

* Cleanup & simplify EnumType

* Use setValue internally to fromU8a

* Add RuntimeVersion

* Expose RuntimeVersion
This commit is contained in:
Jaco Greeff
2018-09-27 12:42:20 +02:00
committed by GitHub
parent 79860b6b30
commit 773358195a
54 changed files with 825 additions and 142 deletions
+2 -2
View File
@@ -36,7 +36,7 @@ describe('BftHashSignature', () => {
describe('Justification', () => {
const just = new Justification({
round_number: 32,
round: 32,
hash: '0xabcd',
signatures: [
{ authorityId: '0x1234', signature: '0x5678' },
@@ -53,7 +53,7 @@ describe('Justification', () => {
});
it('has the correct signatures', () => {
const sig = just.signatures.at(1);
const sig = just.signatures.get(1);
expect(sig.authorityId.toHex()).toEqual('0x9876');
});
+10 -9
View File
@@ -5,6 +5,7 @@
import { AnyNumber, AnyU8a } from './types';
import Struct from './codec/Struct';
import Tuple from './codec/Tuple';
import Vector from './codec/Vector';
import AuthorityId from './AuthorityId';
import Hash from './Hash';
@@ -18,8 +19,8 @@ export type BftAuthoritySignatureValue = {
// Represents a Bft Hash and Signature pairing, typically used in reporting
// network behaviour.
export class BftAuthoritySignature extends Struct {
constructor (value: BftAuthoritySignatureValue = {}) {
export class BftAuthoritySignature extends Tuple {
constructor (value?: BftAuthoritySignatureValue) {
super({
authorityId: AuthorityId,
signature: Signature
@@ -42,8 +43,8 @@ export type BftHashSignatureValue = {
// Represents a Bft Hash and Signature pairing, typically used in reporting
// network behaviour.
export class BftHashSignature extends Struct {
constructor (value: BftHashSignatureValue = {}) {
export class BftHashSignature extends Tuple {
constructor (value?: BftHashSignatureValue) {
super({
hash: Hash,
signature: Signature
@@ -60,20 +61,20 @@ export class BftHashSignature extends Struct {
}
export type JustificationValue = {
round_number?: AnyNumber,
round?: AnyNumber,
hash?: AnyU8a,
signatures?: Array<BftAuthoritySignatureValue>
};
export class Justification extends Struct {
constructor (value: JustificationValue = {}) {
constructor (value?: JustificationValue) {
super({
// FIXME Rust returns this as "round_number", we actually want a JSON alias
// in the structure to take care of these renames...
round_number: U32,
round: U32,
hash: Hash,
signatures: Vector.with(BftAuthoritySignature)
}, value);
}, value, new Map([['round', 'round_number']]));
}
get hash (): Hash {
@@ -81,7 +82,7 @@ export class Justification extends Struct {
}
get round (): U32 {
return this.raw.round_number as U32;
return this.raw.round as U32;
}
get signatures (): Vector<BftAuthoritySignature> {
+1 -1
View File
@@ -45,7 +45,7 @@ describe('Block', () => {
block.extrinsics.length
).toEqual(1); // eslint-disable-line
expect(
block.extrinsics.at(0).length
block.extrinsics.get(0).length
).toEqual(111); // eslint-disable-line
});
+5 -6
View File
@@ -7,8 +7,7 @@ import { AnyU8a } from './types';
import blake2Asu8a from '@polkadot/util-crypto/blake2/asU8a';
import Struct from './codec/Struct';
import Vector from './codec/Vector';
import Extrinsic from './Extrinsic';
import Extrinsics from './Extrinsics';
import Hash from './Hash';
import Header, { HeaderValue } from './Header';
@@ -19,15 +18,15 @@ export type BlockValue = {
// A block encoded with header and extrinsics
export default class Block extends Struct {
constructor (value: BlockValue = {}) {
constructor (value?: BlockValue) {
super({
header: Header,
extrinsics: Vector.with(Extrinsic)
extrinsics: Extrinsics
}, value);
}
get extrinsics (): Vector<Extrinsic> {
return this.raw.extrinsics as Vector<Extrinsic>;
get extrinsics (): Extrinsics {
return this.raw.extrinsics as Extrinsics;
}
// convenience, encodes the header and returns the actual hash
+10
View File
@@ -0,0 +1,10 @@
// 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 Vector from './codec/Vector';
import Extrinsic from './Extrinsic';
// A list of extrinsics
export default class Extrinsics extends Vector.with(Extrinsic) {
}
+9
View File
@@ -0,0 +1,9 @@
// 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 U64 from './U64';
// Gas for contracts
export default class Gas extends U64 {
}
+2 -2
View File
@@ -27,7 +27,7 @@ export type HeaderValue = {
// A block header digest.
export class Digest extends Struct {
constructor (value: DigestValue = {}) {
constructor (value?: DigestValue) {
super({
logs: Vector.with(Bytes)
}, value);
@@ -40,7 +40,7 @@ export class Digest extends Struct {
// A block header.
export default class Header extends Struct {
constructor (value: HeaderValue = {}) {
constructor (value?: HeaderValue) {
super({
parentHash: Hash,
number: BlockNumber,
+12 -1
View File
@@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import KeyValue from './KeyValue';
import KeyValue, { KeyValueOption } from './KeyValue';
describe('KeyValue', () => {
it('decodes KeyValue from u8a', () => {
@@ -47,3 +47,14 @@ describe('KeyValue', () => {
expect(kv.value.toHex()).toEqual('0x998877665544332211');
});
});
describe('KeyValueOption', () => {
it('exposes the properties for key/value', () => {
const kv = new KeyValueOption({
key: '0x11223344'
});
expect(kv.key.toHex()).toEqual('0x11223344');
expect(kv.value.isEmpty).toEqual(true);
});
});
+34 -9
View File
@@ -4,9 +4,10 @@
import { AnyU8a } from './types';
import Option from './codec/Option';
import Struct from './codec/Struct';
import Bytes from './Bytes';
import StorageData from './StorageData';
import StorageKey from './StorageKey';
type KeyValueValue = {
key?: AnyU8a,
@@ -18,18 +19,42 @@ type KeyValueValue = {
// for the keys and values. (Not to be confused with the KeyValue in Metadata, that
// is actually for Maps, whereas this is a representation of actaul storage values)
export default class KeyValue extends Struct {
constructor (value: KeyValueValue = {}) {
constructor (value?: KeyValueValue) {
super({
key: Bytes,
value: Bytes
key: StorageKey,
value: StorageData
}, value);
}
get key (): Bytes {
return this.raw.key as Bytes;
get key (): StorageKey {
return this.raw.key as StorageKey;
}
get value (): Bytes {
return this.raw.value as Bytes;
get value (): StorageData {
return this.raw.value as StorageData;
}
}
export type KeyValueOptionValue = {
key?: AnyU8a,
value?: AnyU8a
};
// A key/value change. This is similar to the KeyValue structure,
// however in this case the value could be optional.
export class KeyValueOption extends Struct {
constructor (value?: KeyValueOptionValue) {
super({
key: StorageKey,
value: Option.with(StorageData)
}, value);
}
get key (): StorageKey {
return this.raw.key as StorageKey;
}
get value (): Option<StorageData> {
return this.raw.value as Option<StorageData>;
}
}
+8 -13
View File
@@ -174,14 +174,11 @@ export class StorageFunctionType$Map extends Struct {
}
export class StorageFunctionType extends EnumType<Type | StorageFunctionType$Map> {
// NOTE Since this is called dynamically, we may have empty values here
constructor (index?: number, value?: any) {
super([
Type,
StorageFunctionType$Map
], ['Plain', 'Map']);
this.setValue(index, value);
], index, value);
}
get isMap (): boolean {
@@ -271,22 +268,20 @@ export default class RuntimeMetadata extends Struct {
}, value);
}
// We receive this a an Array<number> in the JSON output from the Node. Convert
// We receive this as an Array<number> in the JSON output from the Node. Convert
// to u8a and use the fromU8a to do the actual parsing
//
// FIXME Currently toJSON creates a struct, so it is not a one-to-one mapping
// with what is actually found on the RPC layer. This needs to be adjusted to
// match. (However for now, it is useful in debugging)
fromJSON (input: Array<number>): RuntimeMetadata {
return this.fromU8a(
Uint8Array.from(input)
) as RuntimeMetadata;
}
// FIXME Really not crazy about having to manually add all the getters. Preferably it should
// be done automagically in the actual Struct - 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.
// FIXME Currently toJSON creates a struct, so it is not a one-to-one mapping
// with what is actually found on the RPC layer. This needs to be adjusted to
// match fromJSON. (However for now, it is useful in debugging)
toJSON (): any {
return super.toJSON();
}
get events (): Vector<OuterEventMetadataEvent> {
return (this.raw.outerEvent as OuterEventMetadata).events;
+7 -9
View File
@@ -25,7 +25,7 @@ type BftAtReportValue = {
// items in the structure is called, except a & b (one should be expected, the
// other actual)
export class BftAtReport extends Struct {
constructor (value: BftAtReportValue = {}) {
constructor (value?: BftAtReportValue) {
super({
round: U32,
a: BftHashSignature,
@@ -55,13 +55,11 @@ export class BftDoubleCommit extends BftAtReport {
}
export class MisbehaviorKind extends EnumType<BftDoublePrepare | BftDoubleCommit> {
constructor (index: number, value: BftAtReportValue = {}) {
super([
BftDoublePrepare,
BftDoubleCommit
], [], [0x11, 0x12]);
this.setValue(index, value);
constructor (index: number = 0x11, value?: BftAtReportValue) {
super({
0x11: BftDoublePrepare,
0x12: BftDoubleCommit
}, index, value);
}
}
@@ -72,7 +70,7 @@ type MisbehaviorReportValue = {
// A Misbehaviour report against a specific AuthorityId
export default class MisbehaviorReport extends Struct {
constructor (value: MisbehaviorReportValue = {}) {
constructor (value?: MisbehaviorReportValue) {
super({
parentHash: Hash,
parentNumber: BlockNumber,
+46
View File
@@ -0,0 +1,46 @@
// 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 Moment from './Moment';
import U64 from './U64';
describe('Moment', () => {
it('constructs via Date', () => {
expect(
new Moment(
new Date(1537968546280)
).toString()
).toEqual('2018-09-26T13:29:07.000Z');
});
it('wraps from another Moment', () => {
expect(
new Moment(
new Moment(1234)
).toJSON()
).toEqual(1234);
});
it('reads values from JSON', () => {
expect(
new Moment().fromJSON(42).toNumber()
).toEqual(42);
});
it('reads values from u8a', () => {
expect(
new Moment().fromU8a(
new U64(69).toU8a()
).toNumber()
).toEqual(69);
});
it('creates valid U64 u8a outputs', () => {
expect(
new U64().fromU8a(
new Moment(421).toU8a()
).toNumber()
).toEqual(421);
});
});
+69
View File
@@ -0,0 +1,69 @@
// 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 Base from './codec/Base';
const BITLENGTH = 64;
// A wrapper around seconds/timestamps. Internally the representation only has
// second precicion (aligning with Rust), so any numbers passed an/out are always
// per-second. For any encoding/decoding the 1000 multiplier would be applied to
// get it in line with JavaScript formats
export default class Moment extends Base<Date> {
// NOTE
constructor (value: Moment | Date | number = 0) {
super(
value instanceof Date
? new Date(Math.ceil(value.getTime() / 1000) * 1000)
: Moment.decode(value)
);
}
static decode (value: Moment | number | BN): Date {
return value instanceof Moment
? value.raw
: new Date(
bnToBn(value).toNumber() * 1000
);
}
byteLength (): number {
return BITLENGTH / 8;
}
fromJSON (input: any): Moment {
this.raw = Moment.decode(input);
return this;
}
fromU8a (input: Uint8Array): Moment {
this.raw = Moment.decode(
u8aToBn(input.subarray(0, this.byteLength()), true)
);
return this;
}
toJSON (): any {
return this.toNumber();
}
toU8a (): Uint8Array {
return bnToU8a(this.toNumber(), BITLENGTH, true);
}
toString (): string {
return this.raw.toISOString();
}
toNumber (): number {
return Math.ceil(this.raw.getTime() / 1000);
}
}
@@ -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 NewAccountOutcome from './NewAccountOutcome';
describe('NewAccountOutcome', () => {
it('allows setting value in constructor', () => {
expect(
new NewAccountOutcome(2).toNumber()
).toEqual(2);
});
});
@@ -0,0 +1,16 @@
// 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 Enum from './codec/Enum';
// Enum to track the outcome for creation of an account
export default class NewAccountOutcome extends Enum {
constructor (index?: number) {
super([
'NoHint',
'GoodHint',
'BadHint'
], index);
}
}
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2017-2018 @polkadot/api-codec authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import Origin from './Origin';
describe('Origin', () => {
it('does not allow construction', () => {
expect(
() => new Origin()
).toThrow(/placeholder/);
});
});
+14
View File
@@ -0,0 +1,14 @@
// 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 Base from './codec/Base';
// Where Origin occurs, it should be ignored, so it should never actually be constructed
export default class Origin extends Base {
constructor () {
super();
throw new Error('Origin should not be constructed, it is only a placeholder for compatibility');
}
}
+9
View File
@@ -0,0 +1,9 @@
// 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 U64 from './U64';
// Identifier for a deployed parachain
export default class ParachainId extends U64 {
}
@@ -0,0 +1,9 @@
// 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 Extrinsics from './Extrinsics';
// A list of pending extrinsics
export default class PendingExtrinsics extends Extrinsics {
}
+12
View File
@@ -0,0 +1,12 @@
// 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 U32 from './U32';
// Parts per billion (see also Permill)
//
// TODO We need to think about the toNumber() and toString() here, so we
// want to multiply by 1_000_000_000 for those purposes?
export default class Perbill extends U32 {
}
+12
View File
@@ -0,0 +1,12 @@
// 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 U32 from './U32';
// Parts per million (See also Perbill)
//
// TODO We need to think about the toNumber() and toString() here, so we
// want to multiply by 1_000_000 for those purposes?
export default class Permill extends U32 {
}
@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"apis":[[[105,110,104,101,114,101,110,116],1],[[118,97,108,105,100,97,116,120],1]],"authoring_version":1,"impl_name":"substrate-node","impl_version":0,"spec_name":"node","spec_version":1},"id":1}
@@ -0,0 +1,31 @@
// 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 rpc from './RuntimeVersion.rpc.json';
import RuntimeVersion from './RuntimeVersion';
describe('RuntimeVersion', () => {
const version = new RuntimeVersion().fromJSON(rpc.result);
it('has the correct authoring', () => {
expect(version.authoringVersion.toNumber()).toEqual(1);
});
it('has the apis', () => {
const api = version.apis.get(0);
expect(api.id.toHex()).toEqual('0x696e686572656e74');
expect(api.version.toNumber()).toEqual(1);
});
it('has the correct implementation', () => {
expect(version.implName.toString()).toEqual('substrate-node');
expect(version.implVersion.toNumber()).toEqual(0);
});
it('has the correct spec', () => {
expect(version.specName.toString()).toEqual('node');
expect(version.specVersion.toNumber()).toEqual(1);
});
});
+92
View File
@@ -0,0 +1,92 @@
// 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 { AnyNumber, AnyU8a } from './types';
import Struct from './codec/Struct';
import Tuple from './codec/Tuple';
import Vector from './codec/Vector';
import Text from './Text';
import U32 from './U32';
import U8aFixed from '@polkadot/api-codec/codec/U8aFixed';
class ApiId extends U8aFixed {
constructor (value?: AnyU8a) {
super(value, 64);
}
}
type RuntimeVersionApiValue = {
id?: AnyU8a,
version?: AnyNumber
};
class RuntimeVersionApi extends Tuple {
constructor (value?: RuntimeVersionApiValue) {
super({
id: ApiId,
version: U32
}, value);
}
get id (): ApiId {
return this.raw.id as ApiId;
}
get version (): U32 {
return this.raw.version as U32;
}
}
type RuntimeVersionValue = {
specName?: string,
implName?: string,
authoringVersion?: AnyNumber,
specVersion?: AnyNumber,
implVersion?: AnyNumber,
apis?: Array<RuntimeVersionApiValue>
};
export default class RuntimeVersion extends Struct {
constructor (value?: RuntimeVersionValue) {
super({
specName: Text,
implName: Text,
authoringVersion: U32,
specVersion: U32,
implVersion: U32,
apis: Vector.with(RuntimeVersionApi)
}, value, new Map([
['authoringVersion', 'authoring_version'],
['implName', 'impl_name'],
['implVersion', 'impl_version'],
['specName', 'spec_name'],
['specVersion', 'spec_version']
]));
}
get apis (): Vector<RuntimeVersionApi> {
return this.raw.apis as Vector<RuntimeVersionApi>;
}
get authoringVersion (): U32 {
return this.raw.authoringVersion as U32;
}
get implName (): Text {
return this.raw.implName as Text;
}
get implVersion (): U32 {
return this.raw.implVersion as U32;
}
get specName (): Text {
return this.raw.specName as Text;
}
get specVersion (): U32 {
return this.raw.specVersion as U32;
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ type SignedBlockValue = {
};
export default class SignedBlock extends Struct {
constructor (value: SignedBlockValue = {}) {
constructor (value?: SignedBlockValue) {
super({
block: Block,
justification: Justification
@@ -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 StorageChangeSet from './StorageChangeSet';
describe('StorageChangeSet', () => {
const set = new StorageChangeSet({
hash: '0x1234',
changes: [
{ key: '0xab', value: '0xcd' }
]
});
it('wraps hash', () => {
expect(
set.hash.toHex()
).toEqual('0x1234');
});
it('wraps key/value', () => {
expect(
set.changes.get(0).value.toString()
).toEqual('0xcd');
});
});
@@ -0,0 +1,34 @@
// 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 { AnyU8a } from './types';
import Struct from './codec/Struct';
import Vector from './codec/Vector';
import Hash from './Hash';
import { KeyValueOption, KeyValueOptionValue } from './KeyValue';
type StorageChangeSetValue = {
hash?: AnyU8a,
changes?: Array<KeyValueOptionValue>
};
// A set of storage changes. It contains the hash/block and
// a list of the actual changes that took place
export default class StorageChangeSet extends Struct {
constructor (value?: StorageChangeSetValue) {
super({
hash: Hash,
changes: Vector.with(KeyValueOption)
}, value);
}
get changes (): Vector<KeyValueOption> {
return this.raw.changes as Vector<KeyValueOption>;
}
get hash (): Hash {
return this.raw.hash as Hash;
}
}
+8
View File
@@ -0,0 +1,8 @@
// 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 Bytes from './Bytes';
export default class StorageData extends Bytes {
}
+9
View File
@@ -0,0 +1,9 @@
// 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 Bytes from './Bytes';
// A representation of a storage key (typically hashed) in the system
export default class StorageKey extends Bytes {
}
+1 -1
View File
@@ -42,7 +42,7 @@ export default class Text extends Base<string> {
}
fromJSON (input: any): Text {
this.raw = `${input}`;
this.raw = `${input || ''}`;
return this;
}
+14 -3
View File
@@ -6,6 +6,8 @@ import Text from './Text';
type Mapper = (value: string) => string;
const ALLOWED_BOXES = ['Vec', 'Option'];
// 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.
@@ -44,6 +46,8 @@ export default class Type extends Text {
this._removeWrap('Box'),
// remove generics, `MisbehaviorReport<Hash, BlockNumber>` -> `MisbehaviorReport`
this._removeGenerics(),
// alias String -> Text (compat with jsonrpc methods)
this._alias('String', 'Text'),
// alias Vec<u8> -> Bytes
this._alias('Vec<u8>', 'Bytes')
// TODO Check these for possibly matching -
@@ -89,9 +93,16 @@ export default class Type extends Text {
return (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 = this._findClosing(value, start);
// check against the allowed wrappers, be it Vec<..>, Option<...> ...
const box = ALLOWED_BOXES.find((box) => {
const start = index - box.length;
return start >= 0 && value.substr(start, box.length) === box;
});
// we have not found anything, unwrap generic innards
if (!box) {
const end = this._findClosing(value, index + 1);
value = `${value.substr(0, index)}${value.substr(end + 1)}`;
}
+1 -1
View File
@@ -14,7 +14,7 @@ type ValidatorPrefsValue = {
};
export default class ValidatorPrefs extends Struct {
constructor (value: ValidatorPrefsValue = {}) {
constructor (value?: ValidatorPrefsValue) {
super({
unstakeThreshold: U32,
validatorPayment: Balance
@@ -0,0 +1,19 @@
// 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 VoteThreshold from './VoteThreshold';
describe('VoteThreshold', () => {
it('starts with default value', () => {
expect(
new VoteThreshold().toString()
).toEqual('Super majority approval');
});
it('allows setting value in constructor', () => {
expect(
new VoteThreshold(2).toNumber()
).toEqual(2);
});
});
+16
View File
@@ -0,0 +1,16 @@
// 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 Enum from './codec/Enum';
// Voting threshold, used inside proposals to set change the voting tally
export default class VoteThreshold extends Enum {
constructor (index?: number) {
super([
'Super majority approval',
'Super majority rejection',
'Simple majority'
], index);
}
}
+10 -4
View File
@@ -4,6 +4,10 @@
import Base from './Base';
type EnumDef = {
[index: number]: string
} | Array<string>;
// 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
@@ -12,16 +16,16 @@ import Base from './Base';
// TODO:
// - It would be great if this could actually wrap actual TS enums
export default class Enum extends Base<number> {
private _strings: Array<string>;
private _enum: EnumDef;
constructor (strings: Array<string>, value: Enum | number = 0) {
constructor (def: EnumDef, value: Enum | number = 0) {
super(
value instanceof Enum
? value.raw
: value
);
this._strings = strings;
this._enum = def;
}
byteLength (): number {
@@ -29,12 +33,14 @@ export default class Enum extends Base<number> {
}
fromJSON (input: any): Enum {
// FIXME We potentially want to assert that the value is actually inside this._enum
this.raw = input;
return this;
}
fromU8a (input: Uint8Array): Enum {
// FIXME We potentially want to assert that the value is actually inside this._enum
this.raw = input[0];
return this;
@@ -53,6 +59,6 @@ export default class Enum extends Base<number> {
}
toString (): string {
return this._strings[this.raw] || `${this.raw}`;
return this._enum[this.raw] || `${this.raw}`;
}
}
@@ -10,7 +10,7 @@ describe('Struct', () => {
it('provides a clean toString() (raw)', () => {
expect(
new EnumType(
[Text, U32], ['Stringy', 'Inty']
[Text, U32]
).fromU8a(new Uint8Array([0, 2 << 2, 49, 50])).raw.toString()
).toEqual('12');
});
@@ -26,7 +26,7 @@ describe('Struct', () => {
it('allows checking against defined indexes', () => {
expect(
new EnumType(
[Text, U32], [], [1, 5]
{ 1: Text, 5: U32 }
).fromU8a(new Uint8Array([1, 2 << 2, 49, 50])).toString()
).toEqual('Text');
});
+24 -15
View File
@@ -4,6 +4,11 @@
import Base from './Base';
type TypesArray = Array<{ new(value?: any): Base }>;
type TypesDef = {
[index: number]: { new(value?: any): Base }
} | TypesArray;
// 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.
//
@@ -12,24 +17,24 @@ import Base from './Base';
// - 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 EnumType <T> extends Base<Base<T>> {
private _Types: Array<{ new(value?: any): Base }>;
private _Types: TypesArray;
private _index: number;
private _indexes: Array<number>;
private _strings: Array<string>;
constructor (Types: Array<{ new(value?: any): Base }>, strings: Array<string> = [], indexes: Array<number> = []) {
constructor (def: TypesDef, index?: number, value?: any) {
super(
new Types[0]()
new (Object.values(def)[0])()
);
this._Types = Types;
this._indexes = Types.map((Type, index) =>
indexes[index] || index
this._Types = Array.isArray(def)
? def
: Object.values(def);
this._indexes = Object.keys(def).map((index) =>
parseInt(index, 10)
);
this._index = this._indexes[0];
this._strings = Types.map((Type, index) =>
strings[index] || Type.name
);
this.setValue(index, value);
}
get Type (): string {
@@ -41,15 +46,19 @@ export default class EnumType <T> extends Base<Base<T>> {
}
fromU8a (input: Uint8Array): EnumType<T> {
this._index = this._indexes.indexOf(input[0]);
this.raw = new this._Types[this._index]().fromU8a(input.subarray(1));
this.setValue(input[0]);
this.raw.fromU8a(input.subarray(1));
return this;
}
setValue (index?: number, value?: any): void {
// NOTE If this is called from constructors, we may have empty values...
this._index = this._indexes.indexOf(index || 0) || 0;
this._index = this._indexes.indexOf(index || 0);
if (this._index === -1) {
this._index = this._indexes[0];
}
this.raw = new this._Types[this._index](value);
}
@@ -62,6 +71,6 @@ export default class EnumType <T> extends Base<Base<T>> {
}
toString (): string {
return this._strings[this._index];
return this._Types[this._index].name;
}
}
@@ -31,4 +31,18 @@ describe('Struct', () => {
baz: 'U32'
});
});
it('exposes the keys/values', () => {
const test = new Struct({
foo: Text,
bar: U32
}, { foo: 'bazzing', bar: 69 });
expect(test.keys()).toEqual(['foo', 'bar']);
expect(
test.values().map((v) =>
v.toString()
)
).toEqual(['bazzing', '0x00000045']);
});
});
+25 -23
View File
@@ -21,9 +21,10 @@ export default class Struct <
// type names, mapped by key, name of Class in S
E = { [K in keyof S]: string }
> extends Base<T> {
private _Types: E;
protected _jsonMap: Map<keyof S, string>;
protected _Types: E;
constructor (Types: S, value: V = {} as V) {
constructor (Types: S, value: V = {} as V, jsonMap: Map<keyof S, string> = new Map()) {
super(
Object
.keys(Types)
@@ -38,6 +39,7 @@ export default class Struct <
}, {} as T)
);
this._jsonMap = jsonMap;
this._Types = Object
.keys(Types)
.reduce((result: E, key) => {
@@ -52,8 +54,8 @@ export default class Struct <
S = { [index: string]: { new(value?: any): Base } }
> (Types: S): { new(value?: any): Struct<S> } {
return class extends Struct<S> {
constructor (value?: any) {
super(Types, value);
constructor (value?: any, jsonMap?: Map<keyof S, string>) {
super(Types, value, jsonMap);
}
};
}
@@ -69,30 +71,21 @@ export default class Struct <
}
fromJSON (input: any): Struct<S, T, V, E> {
// NOTE From Rust, anonymous structures are encoded to Arrays, here
// we handle that case, taking each value in the array and passing it
// (in order) to the actual decoders (See e.g. SignedBlock.spec.json)
const isArrayIn = Array.isArray(input);
Object.keys(this.raw).forEach((key) => {
const jsonKey = this._jsonMap.get(key as any) || key;
Object.keys(this.raw).forEach((key, index) => {
// @ts-ignore as above...
this.raw[key].fromJSON(
isArrayIn
? input[index]
: input[key]
);
this.raw[key].fromJSON(input[jsonKey]);
});
return this;
}
fromU8a (input: Uint8Array): Struct<S, T, V, E> {
Object.keys(this.raw).reduce((offset, key) => {
// @ts-ignore as above...
this.raw[key].fromU8a(input.subarray(offset));
Object.values(this.raw).reduce((offset, entry) => {
entry.fromU8a(input.subarray(offset));
// @ts-ignore as above...
return offset + this.raw[key].byteLength();
return offset + entry.byteLength();
}, 0);
return this;
@@ -100,18 +93,23 @@ export default class Struct <
toJSON (): any {
return Object.keys(this.raw).reduce((json, key) => {
const jsonKey = this._jsonMap.get(key as any) || key;
// @ts-ignore as above...
json[key] = this.raw[key].toJSON();
json[jsonKey] = this.raw[key].toJSON();
return json;
}, {} as any);
}
keys (): Array<string> {
return Object.keys(this.raw);
}
toU8a (): Uint8Array {
return u8aConcat(
...Object.keys(this.raw).map((key) =>
// @ts-ignore as above...
this.raw[key].toU8a()
...Object.values(this.raw).map((entry) =>
entry.toU8a()
)
);
}
@@ -124,4 +122,8 @@ export default class Struct <
return `{${data}}`;
}
values (): Array<Base> {
return Object.values(this.raw);
}
}
@@ -0,0 +1,45 @@
// 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 Text from '../Text';
import U32 from '../U32';
import Tuple from './Tuple';
describe('Tuple', () => {
let tuple;
beforeEach(() => {
tuple = new Tuple({
foo: Text,
bar: U32
}, { foo: 'foo', bar: 69 });
});
it('returns array with toJSON', () => {
expect(
tuple.toJSON()
).toEqual([
'foo',
'0x00000045'
]);
});
it('initialises via JSON array', () => {
const test = new (Tuple.with({
a: Text,
b: U32,
c: Text
}))();
expect(
test.fromJSON([
'bazzing', 32
]).toJSON()
).toEqual([
'bazzing',
'0x00000020',
''
]);
});
});
+39
View File
@@ -0,0 +1,39 @@
// 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 Base from './Base';
import Struct from './Struct';
// A Tuple defines an anonymous Object with key/values - where the values are Base<T> values.It
// is a specialization of the Struct type where the toJSON/fromJSON operates on Array structures,
// while the U8a encoding is handled in the same way as a Struct
export default class Tuple <
// S & T definitions maps to what we have in Struct (naming expanded there)
S = { [index: string]: { new(value?: any): Base } },
T = { [K in keyof S]: Base }
> extends Struct<S, T> {
static with <
S = { [index: string]: { new(value?: any): Base } }
> (Types: S): { new(value?: any): Tuple<S> } {
return class extends Tuple<S> {
constructor (value?: any) {
super(Types, value);
}
};
}
fromJSON (input: any): Tuple<S, T> {
Object.values(this.raw).forEach((entry, index) => {
entry.fromJSON(input[index]);
});
return this;
}
toJSON (): any {
return Object.values(this.raw).map((entry) =>
entry.toJSON()
);
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ import { AnyU8a } from '../types';
import U8a from './U8a';
type BitLength = 256 | 512;
type BitLength = 64 | 128 | 256 | 512;
// A U8a that manages a a sequence of bytes up to the specified bitLength. Not meant
// to be used directly, rather is should be subclassed with the specific lengths.
+1 -1
View File
@@ -44,7 +44,7 @@ describe('Vector', () => {
describe('array-like functions', () => {
it('allows retrieval of a specific item', () => {
expect(
array.at(2).toString()
array.get(2).toString()
).toEqual('345');
});
+4 -4
View File
@@ -53,10 +53,6 @@ export default class Vector <
}, this._length.byteLength());
}
at (index: number): T {
return this.raw[index];
}
filter (fn: (entry: T, index?: number) => any): Array<T> {
return this.raw.filter(fn);
}
@@ -90,6 +86,10 @@ export default class Vector <
return this;
}
get (index: number): T {
return this.raw[index];
}
map <O> (fn: (entry: T, index?: number) => O): Array<O> {
return this.raw.map(fn);
}
+1
View File
@@ -9,4 +9,5 @@
export { default as Base } from './Base';
export { default as Struct } from './Struct';
export { default as Tuple } from './Tuple';
export { default as Vector } from './Vector';
+18 -4
View File
@@ -19,6 +19,9 @@ export { default as BlockNumber } from './BlockNumber';
export { default as Bool } from './Bool';
export { default as Bytes } from './Bytes';
export { default as Extrinsic } from './Extrinsic';
// NOTE Only used internally, exported as PendingExtrinsics
// export { default as Extrinsics } from './Extrinsics';
export { default as Gas } from './Gas';
// NOTE These are currently only used internally, no direct mapping to Rust strings
// export { default as H256 } from './H256';
// export { default as H512 } from './H512';
@@ -27,16 +30,26 @@ export { default as Header } from './Header';
export { default as KeyValue } from './KeyValue';
export { default as Metadata } from './Metadata';
export { default as MisbehaviorReport } from './MisbehaviorReport';
export { default as Moment } from './Moment';
export { default as NewAccountOutcome } from './NewAccountOutcome';
// NOTE Nonce is renamed to Index
export { default as Index } from './Nonce';
export { default as Origin } from './Origin';
export { default as ParachainId } from './ParachainId';
export { default as PendingExtrinsics } from './PendingExtrinsics';
export { default as Permill } from './Permill';
export { default as Perbill } from './Perbill';
export { default as PropIndex } from './PropIndex';
export { default as ProposalIndex } from './ProposalIndex';
export { default as RawAddress } from './RawAddress';
export { default as ReferendumIndex } from './ReferendumIndex';
export { default as RuntimeVersion } from './RuntimeVersion';
export { default as Signature } from './Signature';
export { default as SignedBlock } from './SignedBlock';
// NOTE Text is currently only used internally, export not needed
// export { default as Text } from './Text';
export { default as StorageChangeSet } from './StorageChangeSet';
export { default as StorageData } from './StorageData';
export { default as StorageKey } from './StorageKey';
export { default as Text } from './Text';
// NOTE Type is currently only used internally (possibly in codec-related work)
// export { default as Type } from './Type';
export { default as U8 } from './U8';
@@ -45,5 +58,6 @@ export { default as U32 } from './U32';
export { default as U64 } from './U64';
export { default as U128 } from './U128';
export { default as U256 } from './U256';
export { default as ValidatorPrefx } from './ValidatorPrefs';
export { default as UoteIndex } from './VoteIndex';
export { default as ValidatorPrefs } from './ValidatorPrefs';
export { default as VoteThreshold } from './VoteThreshold';
export { default as VoteIndex } from './VoteIndex';
+1 -1
View File
@@ -43,7 +43,7 @@ const formatters = new Map<Param$Types, FormatterFunction>([
['Bytes', bytesDecode],
['Hash', hashDecode],
['Header', headerDecode],
['MetaData', metaDecode],
['Metadata', metaDecode],
['SignedBlock', blockDecode],
['u64', bnDecode]
]);
@@ -13,27 +13,6 @@ describe.skip('e2e basics', () => {
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()
@@ -0,0 +1,49 @@
// 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 chain', () => {
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 runtime version', () => {
return api.chain
.getRuntimeVersion()
.then((version) => {
console.error('version', version);
})
.catch((error) => {
console.error(error);
throw error;
});
});
});
+1 -1
View File
@@ -11,7 +11,7 @@ import createSection from '@polkadot/params/section';
const pendingExtrinsics: CreateItemOptions = {
description: 'Returns all pending extrinsics, potentially grouped by sender',
params: [],
type: 'PrendingExtrinsics'
type: 'PendingExtrinsics'
};
const submitExtrinsic: CreateItemOptions = {
+15 -1
View File
@@ -30,6 +30,20 @@ const getHeader: CreateItemOptions = {
type: 'Header'
};
const getRuntimeVersion: CreateItemOptions = {
description: ' Get the runtime version',
params: [],
type: 'RuntimeVersion'
};
const getRuntimeVersionAt: CreateItemOptions = {
description: ' Get the runtime version at a specific block',
params: [
param('hash', 'Hash')
],
type: 'RuntimeVersion'
};
const newHead: CreateItemOptions = {
description: 'Retrieves the best header via subscription',
subscribe: [
@@ -43,7 +57,7 @@ const newHead: CreateItemOptions = {
const privateMethods: CreateItemOptionsMap = {};
const publicMethods: CreateItemOptionsMap = {
getBlock, getHead, getHeader, newHead
getBlock, getHead, getHeader, getRuntimeVersion, getRuntimeVersionAt, newHead
};
export type PublicMethods = typeof publicMethods;
+2 -2
View File
@@ -30,7 +30,7 @@ const callAt: CreateItemOptions = {
const getMetadata: CreateItemOptions = {
description: 'Returns the runtime metadata',
params: [],
type: 'MetaData'
type: 'Metadata'
};
const getMetadataAt: CreateItemOptions = {
@@ -38,7 +38,7 @@ const getMetadataAt: CreateItemOptions = {
params: [
param('block', 'Hash')
],
type: 'MetaData'
type: 'Metadata'
};
const getStorage: CreateItemOptions = {
@@ -81,7 +81,7 @@ export default function decodeValue (decode: Decoder, type: Param$Type, _input:
return keyValue(input);
// HACKY, but a stopgap...
case 'MetaData':
case 'Metadata':
return u8a(input, (input as Uint8Array).length * 8, 0);
case 'MisbehaviorReport':
@@ -94,6 +94,9 @@ export default function decodeValue (decode: Decoder, type: Param$Type, _input:
case 'VoteIndex':
return bn(input, 32);
case 'RuntimeVersion':
return u8a(input, 256, 0);
case 'Signature':
return u8a(input, 512, 0);
+3 -3
View File
@@ -14,7 +14,7 @@ export type EncodingVersions = 'poc-1' | 'latest';
// - @polkadot/storage/key/params.ts
// - decode/value/index.js
// - encode/type/index.js
export type Param$Type = 'AccountId' | 'AccountIndex' | 'Balance' | 'BlockNumber' | 'bool' | 'Bytes' | 'Call' | 'CandidateReceipt' | 'Code' | 'Digest' | 'Gas' | 'Hash' | 'Header' | 'KeyValue' | '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 = 'AccountId' | 'AccountIndex' | 'Balance' | 'BlockNumber' | 'bool' | 'Bytes' | 'Call' | 'CandidateReceipt' | 'Code' | 'Digest' | 'Gas' | 'Hash' | 'Header' | 'KeyValue' | 'Metadata' | 'MisbehaviorReport' | 'ParachainId' | 'PendingExtrinsics' | 'PropIndex' | 'Proposal' | 'ReferendumIndex' | 'RuntimeVersion' | '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,7 +48,7 @@ export type BlockDecoded = {
justification: BlockJustificationDecoded
};
export type MetaData = {
export type Metadata = {
};
export type KeyValue = {
@@ -56,7 +56,7 @@ export type KeyValue = {
value: Uint8Array
}
export type Param$Value = Digest | Header | KeyValue | MetaData | 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>>>;