new EventRecord with topics (#902)

* new EventRecord with topics

* Add additional decoding checks (from #612)

* Slightly rework storage parsing (future checks)

* Split storage result formatting (set vs data)

* Allow v3 event parsing

* Max Vector length check

* assert during Event parsing

* Well, HACK-y fallback

* Revert "Well, HACK-y fallback"

This reverts commit edbf61b99da2753bec3a535126660e71bdcd9861.

* Node overrides...

* Add implVersion

* Register types

* Update with polkadot as well

* inject compatibility types

* EventRecord77 in testing

* Conversion with DoubleMap

* E2E tested against v3 & v4

* Filter with some

* Split archive/remove alex tests

* beforeAll -> beforeEach

* Remove .only

* EventRecordV0 -> 77
This commit is contained in:
Jaco Greeff
2019-06-12 16:45:45 +12:00
committed by Ian He
parent e0994f3c5a
commit 2630f378a4
21 changed files with 463 additions and 235 deletions
+16 -14
View File
@@ -11,9 +11,9 @@ import EnumType from '../../codec/EnumType';
import Struct from '../../codec/Struct';
import Vector from '../../codec/Vector';
import Bytes from '../../primitive/Bytes';
import Null from '../../primitive/Null';
import Text from '../../primitive/Text';
import Type from '../../primitive/Type';
import { DoubleMapType } from '../v4/Storage';
export class StorageFunctionModifier extends Enum {
constructor (value?: any) {
@@ -33,6 +33,8 @@ export class StorageFunctionModifier extends Enum {
}
}
export class DoubleMapType extends Null {}
export class MapType extends Struct {
private _isLinked = false;
@@ -72,7 +74,7 @@ export class MapType extends Struct {
export class PlainType extends Type {
}
export class StorageFunctionType extends EnumType<PlainType | MapType | DoubleMapType> {
export class StorageFunctionType extends EnumType<PlainType | MapType> {
constructor (value?: any, index?: number) {
super({
PlainType,
@@ -82,16 +84,11 @@ export class StorageFunctionType extends EnumType<PlainType | MapType | DoubleMa
}
/**
* @description `true` if the storage entry is a doublemap
*/
get isDoubleMap (): boolean {
return this.toNumber() === 2;
}
/**
* @description The value as a double mapped value
* @description The value as a mapped value
*/
get asDoubleMap (): DoubleMapType {
assert(this.isDoubleMap, `Cannot convert '${this.type}' via asDoubleMap`);
return this.value as DoubleMapType;
}
@@ -113,6 +110,13 @@ export class StorageFunctionType extends EnumType<PlainType | MapType | DoubleMa
return this.value as PlainType;
}
/**
* @description `true` if the storage entry is a double map
*/
get isDoubleMap (): boolean {
return this.toNumber() === 2;
}
/**
* @description `true` if the storage entry is a map
*/
@@ -132,10 +136,8 @@ export class StorageFunctionType extends EnumType<PlainType | MapType | DoubleMa
*/
toString (): string {
if (this.isDoubleMap) {
return this.asDoubleMap.value.toString();
}
if (this.isMap) {
return `DoubleMap<${this.asDoubleMap.toString()}>`;
} else if (this.isMap) {
if (this.asMap.isLinked) {
return `(${this.asMap.value.toString()}, Linkage<${this.asMap.key.toString()}>)`;
}
+16 -13
View File
@@ -11,10 +11,10 @@ import Struct from '../../codec/Struct';
import Vector from '../../codec/Vector';
import Bool from '../../primitive/Bool';
import Bytes from '../../primitive/Bytes';
import Null from '../../primitive/Null';
import Text from '../../primitive/Text';
import Type from '../../primitive/Type';
import { PlainType, StorageFunctionModifier } from '../v1/Storage';
import { DoubleMapType } from './../v4/Storage';
// Re-export classes that haven't changed between V1 and V2
export {
@@ -22,6 +22,8 @@ export {
StorageFunctionModifier
};
export class DoubleMapType extends Null {}
export class MapType extends Struct {
constructor (value?: any) {
super({
@@ -53,7 +55,7 @@ export class MapType extends Struct {
}
}
export class StorageFunctionType extends EnumType<PlainType | MapType | DoubleMapType> {
export class StorageFunctionType extends EnumType<PlainType | MapType> {
constructor (value?: any, index?: number) {
super({
PlainType,
@@ -63,16 +65,11 @@ export class StorageFunctionType extends EnumType<PlainType | MapType | DoubleMa
}
/**
* @description `true` if the storage entry is a doublemap
*/
get isDoubleMap (): boolean {
return this.toNumber() === 2;
}
/**
* @description The value as a double mapped value
* @description The value as a mapped value
*/
get asDoubleMap (): DoubleMapType {
assert(this.isDoubleMap, `Cannot convert '${this.type}' via asDoubleMap`);
return this.value as DoubleMapType;
}
@@ -94,6 +91,13 @@ export class StorageFunctionType extends EnumType<PlainType | MapType | DoubleMa
return this.value as PlainType;
}
/**
* @description `true` if the storage entry is a double map
*/
get isDoubleMap (): boolean {
return this.toNumber() === 2;
}
/**
* @description `true` if the storage entry is a map
*/
@@ -113,9 +117,8 @@ export class StorageFunctionType extends EnumType<PlainType | MapType | DoubleMa
*/
toString (): string {
if (this.isDoubleMap) {
return this.asDoubleMap.value.toString();
}
if (this.isMap) {
return `DoubleMap<${this.asDoubleMap.toString()}>`;
} else if (this.isMap) {
if (this.asMap.isLinked) {
return `(${this.asMap.value.toString()}, Linkage<${this.asMap.key.toString()}>)`;
}
+2 -4
View File
@@ -126,10 +126,8 @@ export class StorageFunctionType extends EnumType<PlainType | MapType | DoubleMa
*/
toString (): string {
if (this.isDoubleMap) {
return this.asDoubleMap.value.toString();
}
if (this.isMap) {
return `DoubleMap<${this.asDoubleMap.toString()}>`;
} else if (this.isMap) {
if (this.asMap.isLinked) {
return `(${this.asMap.value.toString()}, Linkage<${this.asMap.key.toString()}>)`;
}
@@ -14,6 +14,10 @@ import { MetadataCall } from '../v1/Calls';
import { MetadataEvent } from '../v1/Events';
import { StorageFunctionMetadata } from './Storage';
export {
MetadataEvent
};
/**
* @name MetadataModule
* @description
+7 -3
View File
@@ -4,7 +4,7 @@
import { Codec, Constructor, ConstructorDef } from '../types';
import { assert, hexToU8a, isHex, isNumber, isObject, isString, isU8a, u8aConcat, u8aToHex } from '@plugnet/util';
import { assert, hexToU8a, isHex, isNumber, isObject, isString, isU8a, isUndefined, u8aConcat, u8aToHex } from '@plugnet/util';
import Null from '../primitive/Null';
import Base from './Base';
@@ -98,15 +98,19 @@ export default class EnumType<T> extends Base<Codec> implements Codec {
const aliasKey = aliasses[keyLower] || keyLower;
const index = keys.indexOf(aliasKey);
assert(index !== -1, `Cannot map input on JSON, unable to find '${key}' in ${keys.join(', ')}`);
assert(index !== -1, `Cannot map EnumType JSON, unable to find '${key}' in ${keys.join(', ')}`);
return EnumType.createValue(def, index, value);
}
private static createValue (def: TypesDef, index: number = 0, value?: any): Decoded {
const Clazz = Object.values(def)[index];
assert(!isUndefined(Clazz), `Unable to create EnumType via index ${index}, in ${Object.keys(def).join(', ')}`);
return {
index,
value: new (Object.values(def)[index])(value)
value: new Clazz(value)
};
}
+7 -4
View File
@@ -2,13 +2,15 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { u8aToU8a } from '@plugnet/util';
import { u8aToU8a, assert } from '@plugnet/util';
import Compact from './Compact';
import { Codec, Constructor } from '../types';
import { decodeU8a } from './utils';
import AbstractArray from './AbstractArray';
const MAX_LENGTH = 32768;
/**
* @name Vector
* @description
@@ -43,10 +45,11 @@ export default class Vector<T extends Codec> extends AbstractArray<T> {
}
const u8a = u8aToU8a(value);
const [offset, _length] = Compact.decodeU8a(u8a);
const length = _length.toNumber();
const [offset, length] = Compact.decodeU8a(u8a);
return decodeU8a(u8a.subarray(offset), new Array(length).fill(Type)) as T[];
assert(length.lten(MAX_LENGTH), `Vector length ${length.toString()} exceeds ${MAX_LENGTH}`);
return decodeU8a(u8a.subarray(offset), new Array(length.toNumber()).fill(Type)) as T[];
}
static with<O extends Codec> (Type: Constructor<O>): Constructor<Vector<O>> {
+9 -2
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 { isString, isU8a, u8aToU8a } from '@plugnet/util';
import { assert, isString, isU8a, u8aToU8a } from '@plugnet/util';
import { AnyU8a } from '../types';
import Compact from '../codec/Compact';
@@ -46,10 +46,17 @@ export default class Bytes extends U8a {
// i.e. new Bytes(new Bytes(...)) will work as expected
return value;
} else if (isU8a(value)) {
if (!value.length) {
return new Uint8Array();
}
// handle all other Uint8Array inputs, these do have a length prefix
const [offset, length] = Compact.decodeU8a(value);
const total = offset + length.toNumber();
return value.subarray(offset, offset + length.toNumber());
assert(total <= value.length, `Bytes: required length less than remainder, expected at least ${total}, found ${value.length}`);
return value.subarray(offset, total);
}
return value;
+10 -3
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 { isString, stringToU8a, u8aToString, u8aToHex } from '@plugnet/util';
import { assert, isString, stringToU8a, u8aToString, u8aToHex } from '@plugnet/util';
import { AnyU8a, Codec } from '../types';
import Compact from '../codec/Compact';
@@ -31,9 +31,16 @@ export default class Text extends String implements Codec {
if (isString(value)) {
return value.toString();
} else if (value instanceof Uint8Array) {
const [offset, length] = Compact.decodeU8a(value);
if (!value.length) {
return '';
}
return u8aToString(value.subarray(offset, offset + length.toNumber()));
const [offset, length] = Compact.decodeU8a(value);
const total = offset + length.toNumber();
assert(total <= value.length, `Text: required length less than remainder, expected at least ${total}, found ${value.length}`);
return u8aToString(value.subarray(offset, total));
}
return `${value}`;
+70 -36
View File
@@ -4,30 +4,40 @@
import { Constructor } from '../types';
import { isUndefined, stringCamelCase, u8aToHex } from '@plugnet/util';
import { assert, isUndefined, stringCamelCase, u8aToHex } from '@plugnet/util';
import Struct from '../codec/Struct';
import Tuple from '../codec/Tuple';
import U8aFixed from '../codec/U8aFixed';
import Null from '../primitive/Null';
import { TypeDef, getTypeClass, getTypeDef } from '../codec/createType';
import MetadataV0 from '../Metadata/v0';
import { EventMetadata } from '../Metadata/v0/Events';
import Null from '../primitive/Null';
import U32 from '../primitive/U32';
import Metadata from '../Metadata';
import { EventMetadata as EventMetadataV0 } from '../Metadata/v0/Events';
import { MetadataEvent as EventMetadataV4 } from '../Metadata/v1/Events';
const EventTypes: { [index: string]: Constructor<EventData> } = {};
/**
* @name EventIndex
* @description
* The Substrate EventIndex representation as a [[U32]].
*/
export class EventIndex extends U32 {
}
/**
* @name EventData
* @description
* Wrapper for the actual data that forms part of an [[Event]]
*/
export class EventData extends Tuple {
private _meta: EventMetadata;
private _meta: EventMetadataV0 | EventMetadataV4;
private _method: string;
private _section: string;
private _typeDef: Array<TypeDef>;
constructor (Types: Array<Constructor>, value: Uint8Array, typeDef: Array<TypeDef>, meta: EventMetadata, section: string, method: string) {
constructor (Types: Array<Constructor>, value: Uint8Array, typeDef: Array<TypeDef>, meta: EventMetadataV0 | EventMetadataV4, section: string, method: string) {
super(Types, value);
this._meta = meta;
@@ -39,7 +49,7 @@ export class EventData extends Tuple {
/**
* @description The wrapped [[EventMetadata]]
*/
get meta (): EventMetadata {
get meta (): EventMetadataV0 | EventMetadataV4 {
return this._meta;
}
@@ -66,12 +76,12 @@ export class EventData extends Tuple {
}
/**
* @name EventIndex
* @name EventId
* @description
* This follows the same approach as in [[Method]], we have the `[sectionIndex, methodIndex]` pairing
* that indicates the actual event fired
*/
export class EventIndex extends U8aFixed {
export class EventId extends U8aFixed {
constructor (value?: any) {
super(value, 16);
}
@@ -90,23 +100,22 @@ export default class Event extends Struct {
const { DataType, value } = Event.decodeEvent(_value);
super({
index: EventIndex,
index: EventId,
data: DataType
}, value);
}
static decodeEvent (value: Uint8Array = new Uint8Array()) {
if (!value.length) {
return {
DataType: Null
};
}
const index = value.subarray(0, 2);
const DataType = EventTypes[index.toString()];
if (isUndefined(DataType)) {
console.error(`Unable to decode event for index ${u8aToHex(index)}`);
return {
DataType: Null,
value: undefined
};
}
assert(!isUndefined(DataType), `Unable to decode event for index ${u8aToHex(index)}`);
return {
DataType,
@@ -119,23 +128,48 @@ 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: MetadataV0): void {
metadata.events.forEach((section, sectionIndex) => {
const sectionName = stringCamelCase(section.name.toString());
static injectMetadata (metadata: Metadata): void {
if (metadata.version === 4) {
let sectionIndex = 0;
section.events.forEach((meta, methodIndex) => {
const methodName = meta.name.toString();
const eventIndex = new Uint8Array([sectionIndex, methodIndex]);
const typeDef = meta.arguments.map((arg) => getTypeDef(arg));
const Types = typeDef.map(getTypeClass);
metadata.asV4.modules.forEach((section) => {
const sectionName = stringCamelCase(section.name.toString());
EventTypes[eventIndex.toString()] = class extends EventData {
constructor (value: Uint8Array) {
super(Types, value, typeDef, meta, sectionName, methodName);
}
};
if (!section.events.isNone && !section.events.isEmpty) {
section.events.unwrap().forEach((meta, methodIndex) => {
const methodName = meta.name.toString();
const eventIndex = new Uint8Array([sectionIndex, methodIndex]);
const typeDef = meta.args.map((arg) => getTypeDef(arg));
const Types = typeDef.map(getTypeClass);
EventTypes[eventIndex.toString()] = class extends EventData {
constructor (value: Uint8Array) {
super(Types, value, typeDef, meta, sectionName, methodName);
}
};
});
sectionIndex += 1;
}
});
});
} else {
metadata.asV0.events.forEach((section, sectionIndex) => {
const sectionName = stringCamelCase(section.name.toString());
section.events.forEach((meta, methodIndex) => {
const methodName = meta.name.toString();
const eventIndex = new Uint8Array([sectionIndex, methodIndex]);
const typeDef = meta.arguments.map((arg) => getTypeDef(arg));
const Types = typeDef.map(getTypeClass);
EventTypes[eventIndex.toString()] = class extends EventData {
constructor (value: Uint8Array) {
super(Types, value, typeDef, meta, sectionName, methodName);
}
};
});
});
}
}
/**
@@ -146,16 +180,16 @@ export default class Event extends Struct {
}
/**
* @description The [[EventIndex]], identifying the raw event
* @description The [[EventId]], identifying the raw event
*/
get index (): EventIndex {
return this.get('index') as EventIndex;
get index (): EventId {
return this.get('index') as EventId;
}
/**
* @description The [[EventMetadata]] with the documentation
*/
get meta (): EventMetadata {
get meta (): EventMetadataV0 | EventMetadataV4 {
return this.data.meta;
}
+2 -2
View File
@@ -18,7 +18,7 @@ describe('EventRecord', () => {
describe('v0', () => {
beforeEach(() => {
Event.injectMetadata(
new Metadata(metadataV0).asV0
new Metadata(metadataV0)
);
});
@@ -53,7 +53,7 @@ describe('EventRecord', () => {
describe.skip('v1', () => {
beforeEach(() => {
Event.injectMetadata(
new Metadata(metadataV1).asV0
new Metadata(metadataV1)
);
});
+48 -2
View File
@@ -6,8 +6,10 @@ import { assert } from '@plugnet/util';
import EnumType from '../codec/EnumType';
import Struct from '../codec/Struct';
import Vector from '../codec/Vector';
import Null from '../primitive/Null';
import U32 from '../primitive/U32';
import Hash from './Hash';
import Event from './Event';
/**
@@ -73,12 +75,12 @@ export class Phase extends EnumType<ApplyExtrinsic | Finalization> {
}
/**
* @name EventRecord
* @name EventRecord77
* @description
* A record for an [[Event]] (as specified by [[Metadata]]) with the specific [[Phase]] of
* application.
*/
export default class EventRecord extends Struct {
export class EventRecord77 extends Struct {
constructor (value: any) {
super({
phase: Phase,
@@ -99,4 +101,48 @@ export default class EventRecord extends Struct {
get phase (): Phase {
return this.get('phase') as Phase;
}
/**
* @description The [[Hash]] topics for this event (empty, compat)
*/
get topics (): Vector<Hash> {
return new (Vector.with(Hash))();
}
}
/**
* @name EventRecord
* @description
* A record for an [[Event]] (as specified by [[Metadata]]) with the specific [[Phase]] of
* application.
*/
export default class EventRecord extends Struct {
constructor (value: any) {
super({
phase: Phase,
event: Event,
topics: Vector.with(Hash)
}, value);
}
/**
* @description The [[Event]] this record refers to
*/
get event (): Event {
return this.get('event') as Event;
}
/**
* @description The [[Phase]] where the event was generated
*/
get phase (): Phase {
return this.get('phase') as Phase;
}
/**
* @description The [[Hash]] topics for this event
*/
get topics (): Vector<Hash> {
return this.get('topics') as Vector<Hash>;
}
}
+10 -3
View File
@@ -5,7 +5,7 @@
import { KeyringPair } from '@plugnet/keyring/types';
import { AnyNumber, AnyU8a, ArgsDef, Codec, IExtrinsic, SignatureOptions } from '../types';
import { isHex, isU8a, u8aToHex, u8aToU8a } from '@plugnet/util';
import { assert, isHex, isU8a, u8aToHex, u8aToU8a } from '@plugnet/util';
import { blake2AsU8a } from '@plugnet/util-crypto';
import Compact from '../codec/Compact';
@@ -58,9 +58,16 @@ export default class Extrinsic extends Struct implements IExtrinsic {
: Compact.addLengthPrefix(u8a)
);
} else if (isU8a(value)) {
const [offset, length] = Compact.decodeU8a(value);
if (!value.length) {
return new Uint8Array();
}
return value.subarray(offset, offset + length.toNumber());
const [offset, length] = Compact.decodeU8a(value);
const total = offset + length.toNumber();
assert(total <= value.length, `Extrinsic: required length less than remainder, expected at least ${total}, found ${value.length}`);
return value.subarray(offset, total);
} else if (value instanceof Method) {
return {
method: value
+2 -3
View File
@@ -25,9 +25,8 @@ export { default as BlockNumber } from './BlockNumber';
export { default as CodeHash } from './CodeHash';
export { default as ContractInfo } from './ContractInfo';
export { default as Digest, DigestItem } from './Digest';
export { default as Event } from './Event';
export { default as EventIndex } from './EventIndex';
export { default as EventRecord } from './EventRecord';
export { default as Event, EventIndex } from './Event';
export { default as EventRecord, EventRecord77 } from './EventRecord';
export { default as Exposure } from './Exposure';
export { default as Extrinsic } from './Extrinsic';
export { default as ExtrinsicEra } from './ExtrinsicEra';