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:
@@ -30,6 +30,7 @@ import { StorageFunction } from '@polkadot/types/primitive/StorageKey';
|
||||
import { assert, compactStripLength, isFunction, isObject, isUndefined, logger, u8aToHex } from '@polkadot/util';
|
||||
import { cryptoWaitReady } from '@polkadot/util-crypto';
|
||||
|
||||
import injectNodeCompat from './nodeCompat';
|
||||
import createSubmittable, { SubmittableExtrinsic } from './SubmittableExtrinsic';
|
||||
|
||||
type MetaDecoration = {
|
||||
@@ -398,6 +399,8 @@ export default abstract class ApiBase<CodecResult, SubscriptionResult> implement
|
||||
this._genesisHash = this._options.source.genesisHash;
|
||||
}
|
||||
|
||||
injectNodeCompat(this._runtimeVersion as RuntimeVersion);
|
||||
|
||||
const extrinsics = extrinsicsFromMeta(this.runtimeMetadata.asV0);
|
||||
const storage = storageFromMeta(this.runtimeMetadata);
|
||||
|
||||
@@ -413,7 +416,7 @@ export default abstract class ApiBase<CodecResult, SubscriptionResult> implement
|
||||
|
||||
// only inject if we are not a clone (global init)
|
||||
if (!this._options.source) {
|
||||
Event.injectMetadata(this.runtimeMetadata.asV0);
|
||||
Event.injectMetadata(this.runtimeMetadata);
|
||||
Method.injectMethods(extrinsics);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017-2019 @polkadot/api 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 { Constructor } from '@polkadot/types/types';
|
||||
|
||||
import { EventRecord77, RuntimeVersion, getTypeRegistry } from '@polkadot/types';
|
||||
|
||||
type Compat = {
|
||||
nodeSpecs: Array<{
|
||||
name: string,
|
||||
version: number
|
||||
}>,
|
||||
types: {
|
||||
[index: string]: Constructor
|
||||
}
|
||||
};
|
||||
|
||||
const types: Array<Compat> = [
|
||||
{
|
||||
nodeSpecs: [
|
||||
{
|
||||
name: 'node',
|
||||
version: 77
|
||||
},
|
||||
{
|
||||
name: 'polkadot',
|
||||
version: 0xffff // don't have a spec version as of yet
|
||||
}
|
||||
],
|
||||
types: {
|
||||
'EventRecord': EventRecord77
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export default function injectNodeCompat ({ specName, specVersion }: RuntimeVersion): void {
|
||||
types
|
||||
.filter(({ nodeSpecs }) =>
|
||||
nodeSpecs.some((spec) =>
|
||||
specName.eq(spec.name) &&
|
||||
specVersion.ltn(spec.version)
|
||||
)
|
||||
)
|
||||
.forEach(({ types }) => {
|
||||
getTypeRegistry().register(types);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2017-2019 @polkadot/api 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 Api from '../../src/promise';
|
||||
|
||||
describe.skip('alex archive queries (local)', () => {
|
||||
let api;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.setTimeout(30000);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
api = await Api.create();
|
||||
|
||||
return api;
|
||||
});
|
||||
|
||||
// https://github.com/polkadot-js/api/issues/845
|
||||
it('retrieves block with no issues', async () => {
|
||||
const metadata = await api.rpc.state.getMetadata('0x6f6f9bba0eed8e3ae9446c37eee763f93118b52a315a7b46090453ba6288da1f');
|
||||
|
||||
console.error(JSON.stringify(metadata, null, 2));
|
||||
|
||||
const block = await api.rpc.chain.getBlock('0x6f6f9bba0eed8e3ae9446c37eee763f93118b52a315a7b46090453ba6288da1f')
|
||||
|
||||
console.error(block);
|
||||
|
||||
expect(block).toBeDefined();
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// https://github.com/polkadot-js/api/issues/846
|
||||
it('handles toJSON with no issues', async (done) => {
|
||||
const signedBlock = await api.rpc.chain.getBlock('0x85c62b581f38cb81c3e443d34392672beb1fb877017fd7237cc87704113259dc');
|
||||
const failed = signedBlock.block.extrinsics.filter((extrinsic) => {
|
||||
try {
|
||||
const json = extrinsic.method.toJSON();
|
||||
|
||||
console.error(json);
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.log(extrinsic.method);
|
||||
console.log(extrinsic.method.keys());
|
||||
console.error(error);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
expect(failed).toHaveLength(0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -2,70 +2,46 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Hash, Header } from '@polkadot/types';
|
||||
|
||||
import Api from '../../src/promise';
|
||||
import WsProvider from '../../../rpc-provider/src/ws';
|
||||
|
||||
import { Hash, Header } from '@polkadot/types';
|
||||
const WS_URL = 'wss://poc3-rpc.polkadot.io/';
|
||||
// const WS_URL = 'wss://substrate-rpc.parity.io/';
|
||||
|
||||
describe.skip('alex queries', () => {
|
||||
let api;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.setTimeout(3000000);
|
||||
jest.setTimeout(30000);
|
||||
});
|
||||
|
||||
describe('Remote RPC queries', () => {
|
||||
beforeAll(async () => {
|
||||
api = await Api.create({
|
||||
provider: new WsProvider('wss://poc3-rpc.polkadot.io/')
|
||||
});
|
||||
|
||||
return api;
|
||||
beforeEach(async () => {
|
||||
api = await Api.create({
|
||||
provider: new WsProvider(WS_URL)
|
||||
});
|
||||
|
||||
it('retrieves the list of validators', (done) => {
|
||||
api.query.staking.validators((res) => {
|
||||
console.log('api.query.staking.validators():', res.toJSON());
|
||||
return api;
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
it('retrieves the list of validators', (done) => {
|
||||
api.query.staking.validators((res) => {
|
||||
console.error(res);
|
||||
|
||||
console.log('api.query.staking.validators():', res.toJSON());
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('retrieves the list of the controllers', (done) => {
|
||||
api.derive.staking.constrollers((res) => {
|
||||
console.log('api.query.staking.controllers():', res.toJSON());
|
||||
it('retrieves a single value', (done) => {
|
||||
api.query.staking.validators('5DuiZFa184E9iCwbh4WjXYvJ88NHvWJbS8SARY8Ev1YEqrri', (res) => {
|
||||
console.error(res);
|
||||
|
||||
expect(res.length).toHaveLength(2);
|
||||
expect(res[0].length).not.toHaveLength(0);
|
||||
expect(res[0].length).toEqual(res[1].length);
|
||||
console.log('api.query.staking.validators(id):', res.toJSON());
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('retrieves a single value', (done) => {
|
||||
api.query.staking.validators('5EF7wdkP9XZq38fu2ioAvxoEhHUJUc1E2KbuSQSMGofRKhCL', (res) => {
|
||||
console.log('api.query.staking.validators(id):', res.toJSON());
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('derives a list of the controllers', (done) => {
|
||||
api.derive.staking.controllers((res) => {
|
||||
console.log('api.derive.staking.controllers:', JSON.stringify(res));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('retrieves the list of nominators', (done) => {
|
||||
let count = 0;
|
||||
api.query.staking.nominators((res) => {
|
||||
console.log(`[${++count}]:: nominators(${res[0].length}):`, res.toJSON());
|
||||
|
||||
// done();
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('Gets the hash of the last finalized header', async (done) => {
|
||||
@@ -83,50 +59,36 @@ describe.skip('alex queries', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('Local archive queries (for debug)', () => {
|
||||
beforeAll(async () => {
|
||||
api = await Api.create();
|
||||
|
||||
return api;
|
||||
});
|
||||
|
||||
// https://github.com/polkadot-js/api/issues/845
|
||||
it('retrieves block with no issues', async () => {
|
||||
const metadata = await api.rpc.state.getMetadata('0x6f6f9bba0eed8e3ae9446c37eee763f93118b52a315a7b46090453ba6288da1f');
|
||||
|
||||
console.error(JSON.stringify(metadata, null, 2));
|
||||
|
||||
const block = await api.rpc.chain.getBlock('0x6f6f9bba0eed8e3ae9446c37eee763f93118b52a315a7b46090453ba6288da1f')
|
||||
|
||||
console.error(block);
|
||||
|
||||
expect(block).toBeDefined();
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// https://github.com/polkadot-js/api/issues/846
|
||||
it('handles toJSON with no issues', async (done) => {
|
||||
const signedBlock = await api.rpc.chain.getBlock('0x85c62b581f38cb81c3e443d34392672beb1fb877017fd7237cc87704113259dc');
|
||||
const failed = signedBlock.block.extrinsics.filter((extrinsic) => {
|
||||
try {
|
||||
const json = extrinsic.method.toJSON();
|
||||
|
||||
console.error(json);
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.log(extrinsic.method);
|
||||
console.log(extrinsic.method.keys());
|
||||
console.error(error);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
expect(failed).toHaveLength(0);
|
||||
it('derives a list of the controllers', (done) => {
|
||||
api.derive.staking.controllers((res) => {
|
||||
console.log('api.derive.staking.controllers:', JSON.stringify(res));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('makes a query at a latest block (specified)', async () => {
|
||||
const header = await api.rpc.chain.getHeader();
|
||||
const events = await api.query.system.events.at(header.hash);
|
||||
|
||||
events.forEach(({ event: { data, method, section }, phase, topics }, index) => {
|
||||
console.error(index, phase.toString(), `: ${section}.${method}`, data.toString(), topics.toString());
|
||||
});
|
||||
|
||||
expect(events.length).not.toEqual(0);
|
||||
});
|
||||
|
||||
it('subscribes to events', (done) => {
|
||||
api.query.system.events((events) => {
|
||||
console.error(JSON.stringify(events));
|
||||
|
||||
events.forEach(({ event: { data, method, section }, phase, topics }, index) => {
|
||||
console.error(index, phase.toString(), `: ${section}.${method}`, data.toString(), topics.toString());
|
||||
});
|
||||
|
||||
expect(events).not.toHaveLength(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,7 +32,7 @@ describe.skip('e2e contracts', () => {
|
||||
describe('incrementer', () => {
|
||||
let abi;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeEach(() => {
|
||||
abi = new ContractAbi(incrementer);
|
||||
});
|
||||
|
||||
@@ -94,7 +94,7 @@ describe.skip('e2e contracts', () => {
|
||||
describe('erc20', () => {
|
||||
let abi;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeEach(() => {
|
||||
abi = abi = new ContractAbi(erc20);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,8 +7,11 @@ import testingPairs from '@polkadot/keyring/testingPairs';
|
||||
import { LinkageResult } from '@polkadot/types/codec/Linkage';
|
||||
|
||||
import Api from '../../src/promise';
|
||||
import WsProvider from '../../../rpc-provider/src/ws';
|
||||
|
||||
const ZERO = new BN(0);
|
||||
const WS_URL = 'ws://127.0.0.1:9944';
|
||||
// const WS_URL = 'wss://poc3-rpc.polkadot.io/';
|
||||
|
||||
describe.skip('e2e queries', () => {
|
||||
const keyring = testingPairs({ type: 'ed25519' });
|
||||
@@ -16,7 +19,9 @@ describe.skip('e2e queries', () => {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
if (!api) {
|
||||
api = await Api.create();
|
||||
api = await Api.create({
|
||||
provider: new WsProvider(WS_URL)
|
||||
});
|
||||
}
|
||||
|
||||
jest.setTimeout(30000);
|
||||
@@ -108,7 +113,7 @@ describe.skip('e2e queries', () => {
|
||||
api.derive.balances.all(
|
||||
keyring.alice.address(),
|
||||
(all) => {
|
||||
expect(all.accountId).toEqual(keyring.alice.address());
|
||||
expect(all.accountId.toString()).toEqual(keyring.alice.address());
|
||||
|
||||
expect(all.freeBalance).toBeDefined();
|
||||
expect(all.freeBalance.gt(ZERO)).toBe(true);
|
||||
@@ -127,10 +132,25 @@ describe.skip('e2e queries', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('makes a query at a specific block', async () => {
|
||||
it('makes a query at a latest block (specified)', async () => {
|
||||
const header = await api.rpc.chain.getHeader();
|
||||
const events = await api.query.system.events.at(header.hash);
|
||||
|
||||
events.forEach(({ event: { data, method, section }, phase, topics }, index) => {
|
||||
console.error(index, phase.toString(), `: ${section}.${method}`, data.toString(), topics.toString());
|
||||
});
|
||||
|
||||
expect(events.length).not.toEqual(0);
|
||||
});
|
||||
|
||||
it('subscribes to events', (done) => {
|
||||
api.query.system.events((events) => {
|
||||
events.forEach(({ event: { data, method, section }, phase, topics }, index) => {
|
||||
console.error(index, phase.toString(), `: ${section}.${method}`, data.toString(), topics.toString());
|
||||
});
|
||||
|
||||
expect(events).not.toHaveLength(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,15 @@ import { ExtError, assert, isFunction, isNull, logger } from '@polkadot/util';
|
||||
|
||||
const l = logger('rpc-core');
|
||||
|
||||
const EMPTY_META = {
|
||||
fallback: undefined,
|
||||
modifier: { isOptional: true },
|
||||
type: {
|
||||
asMap: { isLinked: false },
|
||||
isMap: false
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @name Rpc
|
||||
* @summary The API may use a HTTP or WebSockets provider.
|
||||
@@ -187,64 +196,78 @@ export default class Rpc implements RpcInterface {
|
||||
const base = createType(method.type as string, result);
|
||||
|
||||
if (method.type === 'StorageData') {
|
||||
// single return value (via state.getStorage), decode the value based on the
|
||||
// outputType that we have specified. Fallback to Data on nothing
|
||||
const key = params[0] as StorageKey;
|
||||
const type = key.outputType || 'Data';
|
||||
const Clazz = createClass(type);
|
||||
const meta = key.meta || { fallback: undefined, modifier: { isOptional: true } };
|
||||
|
||||
if (key.meta && key.meta.type.isMap && key.meta.type.asMap.isLinked) {
|
||||
// linked map
|
||||
return new Clazz(base);
|
||||
} else {
|
||||
return meta.modifier.isOptional
|
||||
? new Option(Clazz, isNull(result) ? null : new Clazz(base))
|
||||
: new Clazz(base);
|
||||
}
|
||||
return this.formatStorageData(params[0] as StorageKey, base, isNull(result));
|
||||
} else if (method.type === 'StorageChangeSet') {
|
||||
// multiple return values (via state.storage subscription), decode the values
|
||||
// one at a time, all based on the query types. Three values can be returned -
|
||||
// - Base - There is a valid value, non-empty
|
||||
// - null - The storage key is empty (but in the resultset)
|
||||
// - undefined - The storage value is not in the resultset
|
||||
return (params[0] as Vector<StorageKey>).reduce((result, key: StorageKey) => {
|
||||
// Fallback to Data (i.e. just the encoding) if we don't have a specific type
|
||||
const type = key.outputType || 'Data';
|
||||
const Clazz = createClass(type);
|
||||
return (params[0] as Vector<StorageKey>).reduce((results, key: StorageKey) => {
|
||||
results.push(this.formatStorageSet(key, base as StorageChangeSet));
|
||||
|
||||
// see if we have a result value for this specific key
|
||||
const hexKey = key.toHex();
|
||||
const { value } = (base as StorageChangeSet).changes.find((item) =>
|
||||
item.key.toHex() === hexKey
|
||||
) || { value: null };
|
||||
const meta = key.meta || { fallback: undefined, modifier: { isOptional: true } };
|
||||
|
||||
if (!value) {
|
||||
// if we don't have a value, do not fill in the entry, it will be up to the
|
||||
// caller to sort this out, either ignoring or having a cache for older values
|
||||
result.push(undefined);
|
||||
} else {
|
||||
if (key.meta && key.meta.type.isMap && key.meta.type.asMap.isLinked) {
|
||||
// linked map
|
||||
result.push(new Clazz(value.unwrapOr(null)));
|
||||
} else {
|
||||
if (meta.modifier.isOptional) {
|
||||
// create option either with the existing value, or empty when
|
||||
// there is no value returned
|
||||
result.push(new Option(Clazz, value.isNone ? null : new Clazz(value.unwrap())));
|
||||
} else {
|
||||
// for `null` we fallback to the default value, or create an empty type,
|
||||
// otherwise we return the actual value as retrieved
|
||||
result.push(new Clazz(value.unwrapOr(meta.fallback)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [] as Array<Codec | null | undefined>);
|
||||
return results;
|
||||
}, [] as Array<Codec | undefined>);
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
private formatStorageData (key: StorageKey, base: Codec, isNull: boolean): Codec {
|
||||
// single return value (via state.getStorage), decode the value based on the
|
||||
// outputType that we have specified. Fallback to Data on nothing
|
||||
const type = key.outputType || 'Data';
|
||||
const Clazz = createClass(type);
|
||||
const meta = key.meta || EMPTY_META;
|
||||
let created: Codec;
|
||||
|
||||
if (meta.type.isMap && meta.type.asMap.isLinked) {
|
||||
// linked map
|
||||
created = new Clazz(base);
|
||||
} else {
|
||||
created = meta.modifier.isOptional
|
||||
? new Option(Clazz, isNull ? null : new Clazz(base))
|
||||
: new Clazz(base);
|
||||
}
|
||||
|
||||
// TODO Check that the encoded created value matches that what was supplied
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
private formatStorageSet (key: StorageKey, base: StorageChangeSet): Codec | undefined {
|
||||
// Fallback to Data (i.e. just the encoding) if we don't have a specific type
|
||||
const type = key.outputType || 'Data';
|
||||
const Clazz = createClass(type);
|
||||
|
||||
// see if we have a result value for this specific key
|
||||
const hexKey = key.toHex();
|
||||
const { value } = base.changes.find((item) =>
|
||||
item.key.toHex() === hexKey
|
||||
) || { value: null };
|
||||
const meta = key.meta || EMPTY_META;
|
||||
|
||||
// if we don't have a value, do not fill in the entry, it will be up to the
|
||||
// caller to sort this out, either ignoring or having a cache for older values
|
||||
let created: Codec | undefined;
|
||||
|
||||
if (value) {
|
||||
if (meta.type.isMap && meta.type.asMap.isLinked) {
|
||||
// linked map
|
||||
created = new Clazz(value.unwrapOr(null));
|
||||
} else if (meta.modifier.isOptional) {
|
||||
// create option either with the existing value, or empty when
|
||||
// there is no value returned
|
||||
created = new Option(Clazz, value.isNone ? null : new Clazz(value.unwrap()));
|
||||
} else {
|
||||
// for `null` we fallback to the default value, or create an empty type,
|
||||
// otherwise we return the actual value as retrieved
|
||||
created = new Clazz(value.unwrapOr(meta.fallback));
|
||||
}
|
||||
|
||||
// TODO Check that the encoded created value matches that what was supplied
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import testKeyring from '@polkadot/keyring/testing';
|
||||
import storage from '@polkadot/storage/static';
|
||||
import { Codec } from '@polkadot/types/types';
|
||||
import metadata from '@polkadot/types/Metadata/v0/static';
|
||||
import { Header } from '@polkadot/types';
|
||||
import { Header, RuntimeVersion } from '@polkadot/types';
|
||||
import { bnToU8a, logger, u8aToHex } from '@polkadot/util';
|
||||
import { randomAsU8a } from '@polkadot/util-crypto';
|
||||
|
||||
@@ -43,7 +43,7 @@ export default class Mock implements ProviderInterface {
|
||||
public isUpdating: boolean = true;
|
||||
private requests: { [index: string]: (...params: any[]) => string } = {
|
||||
'chain_getBlockHash': (blockNumber: number): string => '0x1234',
|
||||
'chain_getRuntimeVersion': (): string => '0x1234',
|
||||
'chain_getRuntimeVersion': (): string => new RuntimeVersion().toHex(),
|
||||
'state_getStorage': (storage: MockState$Db, params: Array<any>): string => {
|
||||
return u8aToHex(
|
||||
storage[(params[0] as string)]
|
||||
|
||||
@@ -11,6 +11,7 @@ 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';
|
||||
|
||||
@@ -32,6 +33,8 @@ export class StorageFunctionModifier extends Enum {
|
||||
}
|
||||
}
|
||||
|
||||
export class DoubleMapType extends Null {}
|
||||
|
||||
export class MapType extends Struct {
|
||||
private _isLinked = false;
|
||||
|
||||
@@ -75,10 +78,20 @@ export class StorageFunctionType extends EnumType<PlainType | MapType> {
|
||||
constructor (value?: any, index?: number) {
|
||||
super({
|
||||
PlainType,
|
||||
MapType
|
||||
MapType,
|
||||
DoubleMapType
|
||||
}, value, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The value as a mapped value
|
||||
*/
|
||||
get asDoubleMap (): DoubleMapType {
|
||||
assert(this.isDoubleMap, `Cannot convert '${this.type}' via asDoubleMap`);
|
||||
|
||||
return this.value as DoubleMapType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The value as a mapped value
|
||||
*/
|
||||
@@ -97,6 +110,13 @@ export class StorageFunctionType extends EnumType<PlainType | MapType> {
|
||||
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
|
||||
*/
|
||||
@@ -115,7 +135,9 @@ export class StorageFunctionType extends EnumType<PlainType | MapType> {
|
||||
* @description Returns the string representation of the value
|
||||
*/
|
||||
toString (): string {
|
||||
if (this.isMap) {
|
||||
if (this.isDoubleMap) {
|
||||
return `DoubleMap<${this.asDoubleMap.toString()}>`;
|
||||
} else if (this.isMap) {
|
||||
if (this.asMap.isLinked) {
|
||||
return `(${this.asMap.value.toString()}, Linkage<${this.asMap.key.toString()}>)`;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ 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';
|
||||
@@ -21,6 +22,8 @@ export {
|
||||
StorageFunctionModifier
|
||||
};
|
||||
|
||||
export class DoubleMapType extends Null {}
|
||||
|
||||
export class MapType extends Struct {
|
||||
constructor (value?: any) {
|
||||
super({
|
||||
@@ -56,10 +59,20 @@ export class StorageFunctionType extends EnumType<PlainType | MapType> {
|
||||
constructor (value?: any, index?: number) {
|
||||
super({
|
||||
PlainType,
|
||||
MapType
|
||||
MapType,
|
||||
DoubleMapType
|
||||
}, value, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The value as a mapped value
|
||||
*/
|
||||
get asDoubleMap (): DoubleMapType {
|
||||
assert(this.isDoubleMap, `Cannot convert '${this.type}' via asDoubleMap`);
|
||||
|
||||
return this.value as DoubleMapType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The value as a mapped value
|
||||
*/
|
||||
@@ -78,6 +91,13 @@ export class StorageFunctionType extends EnumType<PlainType | MapType> {
|
||||
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
|
||||
*/
|
||||
@@ -96,7 +116,9 @@ export class StorageFunctionType extends EnumType<PlainType | MapType> {
|
||||
* @description Returns the string representation of the value
|
||||
*/
|
||||
toString (): string {
|
||||
if (this.isMap) {
|
||||
if (this.isDoubleMap) {
|
||||
return `DoubleMap<${this.asDoubleMap.toString()}>`;
|
||||
} else if (this.isMap) {
|
||||
if (this.asMap.isLinked) {
|
||||
return `(${this.asMap.value.toString()}, Linkage<${this.asMap.key.toString()}>)`;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import { Codec, Constructor, ConstructorDef } from '../types';
|
||||
|
||||
import { assert, hexToU8a, isHex, isNumber, isObject, isString, isU8a, u8aConcat, u8aToHex } from '@polkadot/util';
|
||||
import { assert, hexToU8a, isHex, isNumber, isObject, isString, isU8a, isUndefined, u8aConcat, u8aToHex } from '@polkadot/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)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 '@polkadot/util';
|
||||
import { u8aToU8a, assert } from '@polkadot/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>> {
|
||||
|
||||
@@ -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 '@polkadot/util';
|
||||
import { assert, isString, isU8a, u8aToU8a } from '@polkadot/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;
|
||||
|
||||
@@ -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 '@polkadot/util';
|
||||
import { assert, isString, stringToU8a, u8aToString, u8aToHex } from '@polkadot/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}`;
|
||||
|
||||
@@ -4,30 +4,40 @@
|
||||
|
||||
import { Constructor } from '../types';
|
||||
|
||||
import { isUndefined, stringCamelCase, u8aToHex } from '@polkadot/util';
|
||||
import { assert, isUndefined, stringCamelCase, u8aToHex } from '@polkadot/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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,8 +6,10 @@ import { assert } from '@polkadot/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>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { KeyringPair } from '@polkadot/keyring/types';
|
||||
import { AnyNumber, AnyU8a, ArgsDef, Codec, IExtrinsic, SignatureOptions } from '../types';
|
||||
|
||||
import { isHex, isU8a, u8aToHex, u8aToU8a } from '@polkadot/util';
|
||||
import { assert, isHex, isU8a, u8aToHex, u8aToU8a } from '@polkadot/util';
|
||||
import { blake2AsU8a } from '@polkadot/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
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user