From 187c66f60046bdb5266233282ef989ceae308f3a Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sat, 11 Aug 2018 20:50:57 +0200 Subject: [PATCH] Add storage subscription API endpoints (#126) * Add storage subscriptions * StorageResult formatting * Decoding WIP * Storage subsciptions in place * Allow null decodes, ref https://github.com/polkadot-js/apps/issues/215 * e2e tests for subscribeStorage * Cache subscription results, return with changeset --- packages/api-format/src/format.ts | 13 +++- packages/api-format/src/input.ts | 8 ++ packages/api-provider/src/mock/mocks.ts | 2 +- packages/api-provider/src/mock/state.spec.js | 2 +- packages/api-provider/src/mock/state.ts | 4 +- .../api-provider/src/mock/subscribe.spec.js | 12 +-- .../api-provider/src/mock/unsubscribe.spec.js | 12 +-- packages/api-provider/test/e2e.test.js | 59 +++++++++++++++ packages/api-provider/test/e2e.test.ts | 36 --------- packages/api-rx/src/observable/index.ts | 13 +--- packages/api-rx/src/observable/subject.ts | 17 ++++- .../{params.spec.js => formatInput.spec.js} | 16 +++- packages/api/src/create/formatResult.spec.js | 75 +++++++++++++++++++ packages/api/src/create/formatResult.ts | 36 +++++++++ packages/api/src/create/interface.ts | 14 +--- packages/api/src/create/methodSend.spec.js | 6 +- packages/api/src/create/methodSend.ts | 10 +-- .../api/src/create/methodSetStorage.spec.js | 39 ---------- packages/api/src/create/methodSetStorage.ts | 49 ------------ packages/api/src/create/methodSubscribe.ts | 14 ++-- packages/api/src/create/types.d.ts | 10 --- packages/type-extrinsics/src/consensus.ts | 2 +- packages/type-jsonrpc/src/chain.ts | 6 +- packages/type-jsonrpc/src/state.ts | 17 ++++- packages/type-params/package.json | 1 + packages/type-params/src/decode/index.spec.js | 6 +- packages/type-params/src/decode/index.ts | 19 +++-- packages/type-params/src/decode/types.d.ts | 2 +- packages/type-params/src/decode/value/bn.ts | 11 ++- packages/type-params/src/decode/value/bool.ts | 9 ++- packages/type-params/src/decode/value/byte.ts | 11 ++- .../type-params/src/decode/value/bytes.ts | 15 ++-- packages/type-params/src/decode/value/code.ts | 15 ++-- .../type-params/src/decode/value/digest.ts | 13 ++-- .../type-params/src/decode/value/header.ts | 10 ++- .../type-params/src/decode/value/index.ts | 14 +++- .../src/decode/value/keyValue.spec.js | 6 +- .../type-params/src/decode/value/keyValue.ts | 8 +- .../type-params/src/decode/value/method.ts | 10 ++- .../src/decode/value/misbehavior.ts | 8 +- .../src/decode/value/passThrough.ts | 8 +- .../type-params/src/decode/value/string.ts | 19 +++-- packages/type-params/src/decode/value/time.ts | 19 +++-- packages/type-params/src/decode/value/u8a.ts | 11 ++- packages/type-params/src/encode/type/index.ts | 6 +- .../src/encode/type/keyValue.spec.js | 8 +- .../src/encode/type/storageKey.spec.js | 21 ++++++ .../type-params/src/encode/type/storageKey.ts | 12 +++ packages/type-params/src/types.d.ts | 2 +- packages/type-storage/src/key/params.ts | 5 +- 50 files changed, 463 insertions(+), 278 deletions(-) create mode 100644 packages/api-provider/test/e2e.test.js delete mode 100644 packages/api-provider/test/e2e.test.ts rename packages/api/src/create/{params.spec.js => formatInput.spec.js} (68%) create mode 100644 packages/api/src/create/formatResult.spec.js create mode 100644 packages/api/src/create/formatResult.ts delete mode 100644 packages/api/src/create/methodSetStorage.spec.js delete mode 100644 packages/api/src/create/methodSetStorage.ts delete mode 100644 packages/api/src/create/types.d.ts create mode 100644 packages/type-params/src/encode/type/storageKey.spec.js create mode 100644 packages/type-params/src/encode/type/storageKey.ts diff --git a/packages/api-format/src/format.ts b/packages/api-format/src/format.ts index 951b5a1dcf..a03e606dc8 100644 --- a/packages/api-format/src/format.ts +++ b/packages/api-format/src/format.ts @@ -2,18 +2,29 @@ // This software may be modified and distributed under the terms // of the ISC license. See the LICENSE file for details. -import { Param$Types, Param$Type$Array } from '@polkadot/params/types'; +import { Storages } from '@polkadot/storage/types'; +import { SectionItem, Param$Types, Param$Type$Array } from '@polkadot/params/types'; import { FormattersFunctionMap } from './types'; import typeToString from '@polkadot/params/typeToString'; import isUndefined from '@polkadot/util/is/undefined'; import logger from '@polkadot/util/logger'; +import u8aToHex from '@polkadot/util/u8a/toHex'; +import createStorageKey from '@polkadot/storage/key'; import echo from './echo'; const l = logger('api-format'); function formatSingleType (formatters: FormattersFunctionMap, type: Param$Types, value: any): any { + if (type === 'StorageKey') { + const [key, ...params]: [SectionItem, Array] = value; + + return u8aToHex( + createStorageKey(key).apply(null, params) + ); + } + const formatter = formatters.get(type); if (isUndefined(formatter)) { diff --git a/packages/api-format/src/input.ts b/packages/api-format/src/input.ts index 6501907da3..5c169d2b3c 100644 --- a/packages/api-format/src/input.ts +++ b/packages/api-format/src/input.ts @@ -5,6 +5,7 @@ import { Params, Param$Types } from '@polkadot/params/types'; import { FormatterFunction } from './types'; +import assert from '@polkadot/util/assert'; import addressDecode from '@polkadot/util-keyring/address/decode'; import bytesEncode from '@polkadot/primitives/json/bytes/encode'; import hashEncode from '@polkadot/primitives/json/hash/encode'; @@ -19,6 +20,13 @@ const formatters = new Map([ ]); export default function formatInputs (params: Params, values: Array): Array { + const required = params.filter(({ isOptional }) => !isOptional); + const optionalText = params.length + ? ` (${(params.length - required.length) || 'none'} optional)` + : ''; + + assert(values.length >= required.length && values.length <= params.length, `${params.length || 'no'} params expected${optionalText}, found ${values.length} instead`); + const types = params.map(({ type }) => type); return format(formatters, types, values); diff --git a/packages/api-provider/src/mock/mocks.ts b/packages/api-provider/src/mock/mocks.ts index e13524080c..c2291ae0b2 100644 --- a/packages/api-provider/src/mock/mocks.ts +++ b/packages/api-provider/src/mock/mocks.ts @@ -84,6 +84,6 @@ export default function mocks ({ emitter, storage, subscriptions }: MockState): setStorageBn(storage, stateTimestamp.current, Math.floor(Date.now() / 1000)); - updateSubs(subscriptions, 'subscribe_newHead', headerEncode(newHead)); + updateSubs(subscriptions, 'chain_subscribeNewHead', headerEncode(newHead)); }, 5000); } diff --git a/packages/api-provider/src/mock/state.spec.js b/packages/api-provider/src/mock/state.spec.js index 2d9c5d6d2a..dbc0bcc6d7 100644 --- a/packages/api-provider/src/mock/state.spec.js +++ b/packages/api-provider/src/mock/state.spec.js @@ -24,7 +24,7 @@ describe('state', () => { expect( state.subscriptions ).toMatchObject({ - 'subscribe_newHead': { + 'chain_subscribeNewHead': { callbacks: {}, lastValue: null } diff --git a/packages/api-provider/src/mock/state.ts b/packages/api-provider/src/mock/state.ts index 766c4504b1..65f281b24d 100644 --- a/packages/api-provider/src/mock/state.ts +++ b/packages/api-provider/src/mock/state.ts @@ -18,8 +18,8 @@ const SUBSCRIPTIONS: string[] = Array.prototype.concat.apply( .filter((method) => method.isSubscription ) - .map(({ name }) => - `subscribe_${name}` + .map(({ name, section }) => + `${section}_${name}` ) ) ); diff --git a/packages/api-provider/src/mock/subscribe.spec.js b/packages/api-provider/src/mock/subscribe.spec.js index 022c51ba65..6736c78a8e 100644 --- a/packages/api-provider/src/mock/subscribe.spec.js +++ b/packages/api-provider/src/mock/subscribe.spec.js @@ -19,7 +19,7 @@ describe('subscribe', () => { }); it('returns a subscription id', () => { - return subscribe(state, 'subscribe_newHead', [() => void 0]).then((id) => { + return subscribe(state, 'chain_subscribeNewHead', [() => void 0]).then((id) => { expect(id).toEqual(state.subscriptionId); }); }); @@ -27,16 +27,16 @@ describe('subscribe', () => { it('stores the mapping values', () => { const cb = () => void 0; - return subscribe(state, 'subscribe_newHead', [cb]).then((id) => { - expect(state.subscriptionMap[id]).toEqual('subscribe_newHead'); - expect(state.subscriptions['subscribe_newHead'].callbacks[id]).toEqual(cb); + return subscribe(state, 'chain_subscribeNewHead', [cb]).then((id) => { + expect(state.subscriptionMap[id]).toEqual('chain_subscribeNewHead'); + expect(state.subscriptions['chain_subscribeNewHead'].callbacks[id]).toEqual(cb); }); }); it('calls back with the last known value', (done) => { - state.subscriptions['subscribe_newHead'].lastValue = 'testValue'; + state.subscriptions['chain_subscribeNewHead'].lastValue = 'testValue'; - subscribe(state, 'subscribe_newHead', [(_, value) => { + subscribe(state, 'chain_subscribeNewHead', [(_, value) => { expect(value).toEqual('testValue'); done(); }]); diff --git a/packages/api-provider/src/mock/unsubscribe.spec.js b/packages/api-provider/src/mock/unsubscribe.spec.js index 4bdc8acf1a..f63ce456c9 100644 --- a/packages/api-provider/src/mock/unsubscribe.spec.js +++ b/packages/api-provider/src/mock/unsubscribe.spec.js @@ -13,23 +13,23 @@ describe('unsubscribe', () => { beforeEach(() => { state = createState(); - return subscribe(state, 'subscribe_newHead', [() => void 0]).then((_id) => { + return subscribe(state, 'chain_subscribeNewHead', [() => void 0]).then((_id) => { id = _id; }); }); it('fails on unknown ids', () => { - return unsubscribe(state, 'subscribe_newHead', 5).catch((error) => { + return unsubscribe(state, 'chain_subscribeNewHead', 5).catch((error) => { expect(error.message).toMatch(/Unable to find/); }); }); it('unsubscribes successfully', () => { - return unsubscribe(state, 'subscribe_newHead', id); + return unsubscribe(state, 'chain_subscribeNewHead', id); }); it('fails on double unsubscribe', () => { - return unsubscribe(state, 'subscribe_newHead', id) + return unsubscribe(state, 'chain_subscribeNewHead', id) .then(() => unsubscribe(state, id)) .catch((error) => { expect(error.message).toMatch(/Unable to find/); @@ -37,9 +37,9 @@ describe('unsubscribe', () => { }); it('clears the subscriptions', () => { - return unsubscribe(state, 'subscribe_newHead', id).then(() => { + return unsubscribe(state, 'chain_subscribeNewHead', id).then(() => { expect(state.subscriptionMap[id]).not.toBeDefined(); - expect(state.subscriptions['subscribe_newHead'].callbacks[id]).not.toBeDefined(); + expect(state.subscriptions['chain_subscribeNewHead'].callbacks[id]).not.toBeDefined(); }); }); }); diff --git a/packages/api-provider/test/e2e.test.js b/packages/api-provider/test/e2e.test.js new file mode 100644 index 0000000000..f7563f4f67 --- /dev/null +++ b/packages/api-provider/test/e2e.test.js @@ -0,0 +1,59 @@ +// 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 storage from '@polkadot/storage'; + +import createApi from '../../api/src'; +import Ws from '../src/ws'; + +describe.skip('e2e', () => { + let api; + + beforeEach(() => { + jest.setTimeout(30000); + api = createApi(new Ws('ws://127.0.0.1:9944')); + }); + + it('subscribes to chain_subscribeNewHead', (done) => { + let count = 0; + + // tslint:disable-next-line + api.chain + .subscribeNewHead((error, data) => { + if (error) { + return done(error); + } + + expect(data).toBeDefined(); + + if (++count === 3) { + done(); + } + }) + .then((subscriptionId) => { + console.log('newHead: subscriptionId =', subscriptionId); + }); + }); + + it('subscribes to storage', (done) => { + api.state + .subscribeStorage([ + [storage.staking.public.freeBalanceOf, '5Ejbye9R8ygByQPrDSasaUid1munedPZUmg4f118HGmtodGp'], + [storage.staking.public.freeBalanceOf, '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFacT7'] + ], (error, data) => { + if (error) { + return done(error); + } + + expect(data).toHaveLength(2); + expect(data[0].toNumber()).toEqual(0); + expect(data[1].toNumber()).not.toEqual(0); + + done(); + }) + .then((subscriptionId) => { + console.log('newHead: subscriptionId =', subscriptionId); + }); + }); +}); diff --git a/packages/api-provider/test/e2e.test.ts b/packages/api-provider/test/e2e.test.ts deleted file mode 100644 index 62ee6b5fef..0000000000 --- a/packages/api-provider/test/e2e.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -// 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 { ApiInterface } from '../../api/src/types'; - -import createApi from '../../api/src'; -import Ws from '../src/ws'; - -describe.skip('e2e', () => { - let api: ApiInterface; - - beforeEach(() => { - jest.setTimeout(30000); - api = createApi(new Ws('ws://127.0.0.1:9944')); - }); - - it('subscribes to client_newHead', (done) => { - let count = 0; - - // tslint:disable-next-line - api.chain - .newHead((error: Error | null, data: any) => { - console.log('newHead: result =', [error, data]); - - expect(data).toBeDefined(); - - if (++count === 3) { - done(); - } - }) - .then((subscriptionId) => { - console.log('newHead: subscriptionId =', subscriptionId); - }); - }); -}); diff --git a/packages/api-rx/src/observable/index.ts b/packages/api-rx/src/observable/index.ts index 48ffcf030d..48757f4dc4 100644 --- a/packages/api-rx/src/observable/index.ts +++ b/packages/api-rx/src/observable/index.ts @@ -20,15 +20,10 @@ export default function observable (subName: string, name: string, section: ApiI from( section[name] .apply(null, params) - .catch(() => { - // FIXME: swallow error - mostly storage failures, undefined as result. Once nodes - // rollout new versions, can display the error here again (and/or fail explicitly) + .catch((error: Error) => { + console.error(error); + + throw error; }) - // HACK The hammer approach if nothing else works - // .then((value: any) => - // isUndefined(value) || isNull(value) - // ? undefined - // : value - // ) ); } diff --git a/packages/api-rx/src/observable/subject.ts b/packages/api-rx/src/observable/subject.ts index 1c12e9d411..2e12ee020e 100644 --- a/packages/api-rx/src/observable/subject.ts +++ b/packages/api-rx/src/observable/subject.ts @@ -5,18 +5,33 @@ import { ApiInterface$Section } from '@polkadot/api/types'; import { BehaviorSubject, Observable, Subscriber } from 'rxjs'; +import isUndefined from '@polkadot/util/is/undefined'; export default function subscription (name: string, params: Array, section: ApiInterface$Section, unsubCallback?: () => void): BehaviorSubject { const subject = new BehaviorSubject(undefined); Observable .create((observer: Subscriber): Function => { + let cachedResult: any; + const callback = (error: Error | null, result: any) => { if (error) { return; } - observer.next(result); + if (isUndefined(cachedResult) || !Array.isArray(cachedResult)) { + cachedResult = result; + } else { + const resultArray = (result as Array) || []; + + cachedResult = cachedResult.map((cachedValue, index) => + isUndefined(resultArray[index]) + ? cachedValue + : resultArray[index] + ); + } + + observer.next(cachedResult); }; const fn = section[name]; diff --git a/packages/api/src/create/params.spec.js b/packages/api/src/create/formatInput.spec.js similarity index 68% rename from packages/api/src/create/params.spec.js rename to packages/api/src/create/formatInput.spec.js index d43f0407ba..1aa6f135b9 100644 --- a/packages/api/src/create/params.spec.js +++ b/packages/api/src/create/formatInput.spec.js @@ -9,6 +9,12 @@ describe('params', () => { beforeEach(() => { methods = { + array: { + params: [ + { name: 'test', type: ['Bytes'] } + ], + type: 'Bytes' + }, blah: { params: [ { name: 'foo', type: 'Bytes' } @@ -30,9 +36,17 @@ describe('params', () => { ).toThrow(/params expected/); }); - it('check against params (required)', () => { + it('check against params (optional)', () => { expect( params(methods.bleh.params, []) ).toBeDefined(); }); + + it('formats array inputs correctly', () => { + expect( + params(methods.array.params, [[new Uint8Array([1]), new Uint8Array([2])]]) + ).toEqual([[ + '0x01', '0x02' + ]]); + }); }); diff --git a/packages/api/src/create/formatResult.spec.js b/packages/api/src/create/formatResult.spec.js new file mode 100644 index 0000000000..58580e5e6a --- /dev/null +++ b/packages/api/src/create/formatResult.spec.js @@ -0,0 +1,75 @@ +// Copyright 2017-2018 @polkadot/api authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +import storage from '@polkadot/storage'; + +import createApi from '../index'; + +const ADDR_ONE = '5EhmTa7fL6SdjgKXo9g6hetR6nHnRAmrtisoGFWEESjzECtY'; +const ADDR_TWO = '5FjbQAHSptRU4Q7Py6zDsRnE7UkpZikJVg5nm1tmu3WuZXKd'; + +const ENC_ONE = '0x11cf1094db4db43356b7787c3e59c39f'; +const ENC_TWO = '0x924776c687da8e30afdd2ac87d2c67da'; + +describe('formatResult', () => { + let api; + let provider; + + beforeEach(() => { + provider = { + send: jest.fn((method, params) => + Promise.resolve('0x0102') + ), + subscribe: jest.fn((method, params, subscription) => + subscription(null, { + block: '0x1234', + changes: [ + ['0x11cf1094db4db43356b7787c3e59c39f', '0x0102'], + ['0x924776c687da8e30afdd2ac87d2c67da', '0x0201'] + ] + }) + ) + }; + api = createApi(provider); + }); + + it('encodes key (with params), decoding response', () => { + return api.state + .getStorage([ + storage.staking.public.freeBalanceOf, ADDR_ONE + ]) + .then((value) => { + expect( + provider.send + ).toHaveBeenCalledWith( + 'state_getStorage', + [ENC_ONE] + ); + expect(value.toNumber()).toEqual(513); + }); + }); + + it('encodes multiple keys, decoding multiple results', (done) => { + api.state + .subscribeStorage( + [ + [storage.staking.public.freeBalanceOf, ADDR_ONE], + [storage.staking.public.freeBalanceOf, ADDR_TWO] + ], + (error, value) => { + expect( + provider.subscribe + ).toHaveBeenCalledWith( + 'state_subscribeStorage', + [[ENC_ONE, ENC_TWO]], + expect.anything() + ); + expect( + value.map((bn) => bn.toNumber()) + ).toEqual([513, 258]); + + done(error); + }); + }); +}); diff --git a/packages/api/src/create/formatResult.ts b/packages/api/src/create/formatResult.ts new file mode 100644 index 0000000000..cb5cea832c --- /dev/null +++ b/packages/api/src/create/formatResult.ts @@ -0,0 +1,36 @@ +// Copyright 2017-2018 @polkadot/api authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +import { Interfaces } from '@polkadot/jsonrpc/types'; +import { SectionItem } from '@polkadot/params/types'; +import { Storages } from '@polkadot/storage/types'; + +import formatOutput from '@polkadot/api-format/output'; +import decodeParams from '@polkadot/params/decode'; + +function formatStorageOutput (key: SectionItem, result?: any): any { + return decodeParams(key.type, result, 'latest', true).value; +} + +export default function formatResult (method: SectionItem, params: Array, inputs: Array, result?: any): any { + if (method.type === 'StorageResult') { + return formatStorageOutput(inputs[0][0], result); + } + + if (method.type === 'StorageResultSet') { + return params[0].map((key: string, index: number) => { + const input = inputs[0][index][0]; + const { changes = [] }: { block: string, changes: Array<[string, string]> } = result || {}; + const value = changes.find(([_key]) => key === _key); + + if (!value) { + return undefined; + } + + return formatStorageOutput(input, value[1]); + }); + } + + return formatOutput(method.type, result); +} diff --git a/packages/api/src/create/interface.ts b/packages/api/src/create/interface.ts index 220034f426..249ade35fa 100644 --- a/packages/api/src/create/interface.ts +++ b/packages/api/src/create/interface.ts @@ -5,24 +5,14 @@ import { Interface$Sections } from '@polkadot/jsonrpc/types'; import { ProviderInterface } from '@polkadot/api-provider/types'; import { ApiInterface$Section } from '../types'; -import { MethodCreator } from './types'; import interfaces from '@polkadot/jsonrpc/index'; import assert from '@polkadot/util/assert'; import isUndefined from '@polkadot/util/is/undefined'; import methodSend from './methodSend'; -import methodSetStorage from './methodSetStorage'; import methodSubscribe from './methodSubscribe'; -type RpcOverrides = { - [index: string]: MethodCreator -}; - -const overrides: RpcOverrides = { - 'state_getStorage': methodSetStorage -}; - export default function createInterface (provider: ProviderInterface, section: Interface$Sections): ApiInterface$Section { const definition = interfaces[section]; @@ -38,8 +28,8 @@ export default function createInterface (provider: ProviderInterface, section: I const def = methods[name]; exposed[name] = def.isSubscription - ? methodSubscribe(provider, rpcName, name, def) - : (overrides[rpcName] || methodSend)(provider, rpcName, name, def); + ? methodSubscribe(provider, rpcName, def) + : methodSend(provider, rpcName, def); return exposed; }, {} as ApiInterface$Section); diff --git a/packages/api/src/create/methodSend.spec.js b/packages/api/src/create/methodSend.spec.js index fdcc753c5f..56d867ad6a 100644 --- a/packages/api/src/create/methodSend.spec.js +++ b/packages/api/src/create/methodSend.spec.js @@ -32,7 +32,7 @@ describe('methodCall', () => { }); it('wraps errors with the call signature', () => { - const method = createMethod(provider, 'test_blah', 'blah', methods.blah); + const method = createMethod(provider, 'test_blah', methods.blah); return method().catch((error) => { expect(error.message).toMatch(/blah \(foo: Bytes\): Bytes/); @@ -40,7 +40,7 @@ describe('methodCall', () => { }); it('checks for mismatched parameters', () => { - const method = createMethod(provider, 'test_bleh', 'bleh', methods.bleh); + const method = createMethod(provider, 'test_bleh', methods.bleh); return method(1).catch((error) => { expect(error.message).toMatch(/no params expected, found 1 instead/); @@ -48,7 +48,7 @@ describe('methodCall', () => { }); it('calls the provider with the correct parameters', () => { - const method = createMethod(provider, 'test_blah', 'blah', methods.blah); + const method = createMethod(provider, 'test_blah', methods.blah); return method(new Uint8Array([0x12, 0x34])).then(() => { expect(provider.send).toHaveBeenCalledWith('test_blah', ['0x1234']); diff --git a/packages/api/src/create/methodSend.ts b/packages/api/src/create/methodSend.ts index 11e308afb6..bbba189df6 100644 --- a/packages/api/src/create/methodSend.ts +++ b/packages/api/src/create/methodSend.ts @@ -7,20 +7,20 @@ import { SectionItem } from '@polkadot/params/types'; import { ProviderInterface } from '@polkadot/api-provider/types'; import { ApiInterface$Section$Method } from '../types'; -import formatOutput from '@polkadot/api-format/output'; +import formatInputs from '@polkadot/api-format/input'; import ExtError from '@polkadot/util/ext/error'; import signature from '@polkadot/params/signature'; -import createParams from './params'; +import formatResult from './formatResult'; -export default function createMethodSend (provider: ProviderInterface, rpcName: string, name: string, method: SectionItem): ApiInterface$Section$Method { +export default function createMethodSend (provider: ProviderInterface, rpcName: string, method: SectionItem): ApiInterface$Section$Method { const call = async (...values: Array): Promise => { // TODO Warn on deprecated methods try { - const params = createParams(method.params, values); + const params = formatInputs(method.params, values); const result = await provider.send(rpcName, params); - return formatOutput(method.type, result); + return formatResult(method, params, values, result); } catch (error) { throw new ExtError(`${signature(method)}:: ${error.message}`, (error as ExtError).code, undefined); } diff --git a/packages/api/src/create/methodSetStorage.spec.js b/packages/api/src/create/methodSetStorage.spec.js deleted file mode 100644 index 4f079f9baa..0000000000 --- a/packages/api/src/create/methodSetStorage.spec.js +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2017-2018 @polkadot/api authors & contributors -// This software may be modified and distributed under the terms -// of the ISC license. See the LICENSE file for details. - -import storage from '@polkadot/storage'; - -import createApi from '../index'; - -describe('methodSetStorage', () => { - let api; - let provider; - - beforeEach(() => { - provider = { - send: jest.fn((method, params) => { - return Promise.resolve('0x0102'); - }) - }; - api = createApi(provider); - }); - - it('sends without encoding/decoding when encoded key is provided', () => { - return api.state.getStorage(new Uint8Array([1, 2, 3])).then((value) => { - expect(value).toEqual(new Uint8Array([1, 2])); - }); - }); - - it('encodes key (with params), decoding response', () => { - return api.state - .getStorage( - storage.staking.public.freeBalanceOf, - '5EhmTa7fL6SdjgKXo9g6hetR6nHnRAmrtisoGFWEESjzECtY' - ) - .then((value) => { - expect(provider.send).toHaveBeenCalledWith('state_getStorage', ['0x11cf1094db4db43356b7787c3e59c39f']); - expect(value.toNumber()).toEqual(513); - }); - }); -}); diff --git a/packages/api/src/create/methodSetStorage.ts b/packages/api/src/create/methodSetStorage.ts deleted file mode 100644 index a5deb2c3fe..0000000000 --- a/packages/api/src/create/methodSetStorage.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2017-2018 @polkadot/api authors & contributors -// This software may be modified and distributed under the terms -// of the ISC license. See the LICENSE file for details. - -import { Interfaces } from '@polkadot/jsonrpc/types'; -import { SectionItem } from '@polkadot/params/types'; -import { Storages } from '@polkadot/storage/types'; -import { ProviderInterface } from '@polkadot/api-provider/types'; -import { ApiInterface$Section$Method } from '../types'; - -import decodeParams from '@polkadot/params/decode'; -import createStorageKey from '@polkadot/storage/key'; -import ExtError from '@polkadot/util/ext/error'; -import isUndefined from '@polkadot/util/is/undefined'; -import signature from '@polkadot/params/signature'; - -import methodSend from './methodSend'; - -type KeyValues = [SectionItem, any]; -export default function createMethodSetStorage (provider: ProviderInterface, rpcName: string, name: string, method: SectionItem): ApiInterface$Section$Method { - const send = methodSend(provider, rpcName, name, method); - const tryCatch = (fn: () => any): any => { - try { - return fn(); - } catch (error) { - throw new ExtError(`${signature(method)}:: ${error.message}`, (error as ExtError).code, undefined); - } - }; - - const call = async (...values: Array): Promise => { - if (isUndefined(values[0]) || isUndefined(values[0].params)) { - return send.apply(null, values); - } - - const [keyType, ...params] = values as KeyValues; - const key: Uint8Array = tryCatch(() => - createStorageKey(keyType).apply(null, params) - ); - - const result = await send(key); - - return tryCatch(() => - // FIXME We don't do any conversion checks for the type, currently not an issue, but _could_ turn out to be problematic (since this is storage, apply no transforms) - decodeParams(keyType.type, result, 'poc-1').value - ); - }; - - return call as ApiInterface$Section$Method; -} diff --git a/packages/api/src/create/methodSubscribe.ts b/packages/api/src/create/methodSubscribe.ts index 6614ed0cbe..ef078330d1 100644 --- a/packages/api/src/create/methodSubscribe.ts +++ b/packages/api/src/create/methodSubscribe.ts @@ -7,29 +7,29 @@ import { SectionItem } from '@polkadot/params/types'; import { ProviderInterface, ProviderInterface$Callback } from '@polkadot/api-provider/types'; import { ApiInterface$Section$Method } from '../types'; -import formatOutput from '@polkadot/api-format/output'; +import formatInputs from '@polkadot/api-format/input'; import signature from '@polkadot/params/signature'; import assert from '@polkadot/util/assert'; import ExtError from '@polkadot/util/ext/error'; import isFunction from '@polkadot/util/is/function'; -import createParams from './params'; +import formatResult from './formatResult'; -export default function methodSubscribe (provider: ProviderInterface, rpcName: string, name: string, method: SectionItem): ApiInterface$Section$Method { +export default function methodSubscribe (provider: ProviderInterface, rpcName: string, method: SectionItem): ApiInterface$Section$Method { const unsubscribe = (subscriptionId: any): Promise => - provider.send(`unsubscribe_${name}`, [subscriptionId]); + provider.send(rpcName.replace('subscribe', 'unsubscribe'), [subscriptionId]); const call = async (...values: Array): Promise => { try { const cb: ProviderInterface$Callback = values.pop(); assert(isFunction(cb), `Expected callback in last position of params`); - const params = createParams(method.params, values); + const params = formatInputs(method.params, values); const update = (error: Error | null, result?: any) => { - cb(error, formatOutput(method.type, result)); + cb(error, formatResult(method, params, values, result)); }; - return provider.subscribe(`subscribe_${name}`, params, update); + return provider.subscribe(rpcName, params, update); } catch (error) { throw new ExtError(`${signature(method)}:: ${error.message}`, (error as ExtError).code, undefined); } diff --git a/packages/api/src/create/types.d.ts b/packages/api/src/create/types.d.ts deleted file mode 100644 index c2f1b201f6..0000000000 --- a/packages/api/src/create/types.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2017-2018 @polkadot/api authors & contributors -// This software may be modified and distributed under the terms -// of the ISC license. See the LICENSE file for details. - -import { Interfaces } from '@polkadot/jsonrpc/types'; -import { SectionItem } from '@polkadot/params/types'; -import { ProviderInterface } from '@polkadot/api-provider/types'; -import { ApiInterface$Section$Method } from '../types'; - -export type MethodCreator = (provider: ProviderInterface, rpcName: string, name: string, method: SectionItem) => ApiInterface$Section$Method; diff --git a/packages/type-extrinsics/src/consensus.ts b/packages/type-extrinsics/src/consensus.ts index 45557265bd..cbf805be24 100644 --- a/packages/type-extrinsics/src/consensus.ts +++ b/packages/type-extrinsics/src/consensus.ts @@ -27,7 +27,7 @@ const setCode: CreateItemOptions = { const setStorage: CreateItemOptions = { description: 'Set storage entries', params: [ - param('entries', ['KeyValueStorage']) + param('entries', ['StorageKeyValue']) ], type: [] }; diff --git a/packages/type-jsonrpc/src/chain.ts b/packages/type-jsonrpc/src/chain.ts index d73580e3f2..670a5a95a8 100644 --- a/packages/type-jsonrpc/src/chain.ts +++ b/packages/type-jsonrpc/src/chain.ts @@ -22,7 +22,7 @@ const getHeader: CreateItemOptions = { type: 'Header' }; -const newHead: CreateItemOptions = { +const subscribeNewHead: CreateItemOptions = { description: 'Retrieves the best header via subscription', isSubscription: true, params: [], @@ -40,7 +40,7 @@ export default (name: Interface$Sections): Section => createMethod('getHead')(getHead), getHeader: createMethod('getHeader')(getHeader), - newHead: - createMethod('newHead')(newHead) + subscribeNewHead: + createMethod('subscribeNewHead')(subscribeNewHead) } })); diff --git a/packages/type-jsonrpc/src/state.ts b/packages/type-jsonrpc/src/state.ts index 99d461cf91..f281e83f8b 100644 --- a/packages/type-jsonrpc/src/state.ts +++ b/packages/type-jsonrpc/src/state.ts @@ -30,9 +30,9 @@ const callAt: CreateItemOptions = { const getStorage: CreateItemOptions = { description: 'Retrieves the storage for a key', params: [ - param('key', 'Bytes') + param('key', 'StorageKey') ], - type: 'Bytes' + type: 'StorageResult' }; const getStorageAt: CreateItemOptions = { @@ -78,6 +78,15 @@ const getStorageSizeAt: CreateItemOptions = { type: 'u64' }; +const subscribeStorage: CreateItemOptions = { + description: 'Subscribes to storage changes for the provided keys', + isSubscription: true, + params: [ + param('keys', ['StorageKey']) + ], + type: 'StorageResultSet' +}; + /** * @summary Query the state and state storage. */ @@ -100,6 +109,8 @@ export default (name: Interface$Sections): Section => getStorageSize: createMethod('getStorageSize')(getStorageSize), getStorageSizeAt: - createMethod('getStorageSizeAt')(getStorageSizeAt) + createMethod('getStorageSizeAt')(getStorageSizeAt), + subscribeStorage: + createMethod('subscribeStorage')(subscribeStorage) } })); diff --git a/packages/type-params/package.json b/packages/type-params/package.json index 2fdc4c253f..c05dd7cb25 100644 --- a/packages/type-params/package.json +++ b/packages/type-params/package.json @@ -35,6 +35,7 @@ "dependencies": { "@babel/runtime": "^7.0.0-beta.51", "@polkadot/extrinsics": "^0.28.2", + "@polkadot/params": "^0.28.2", "@polkadot/primitives": "^0.28.2", "@polkadot/util": "^0.28.1" } diff --git a/packages/type-params/src/decode/index.spec.js b/packages/type-params/src/decode/index.spec.js index 398fe3b555..7f47daecda 100644 --- a/packages/type-params/src/decode/index.spec.js +++ b/packages/type-params/src/decode/index.spec.js @@ -71,9 +71,9 @@ describe('decode', () => { ).toEqual('{"length":15,"value":["69",[true,false,true]]}'); }); - it('decodes KeyValueStorage arrays', () => { + it('decodes StorageKeyValue arrays', () => { expect( - decode(['KeyValueStorage'], new Uint8Array([ + decode(['StorageKeyValue'], new Uint8Array([ 2, 0, 0, 0, 4, 0, 0, 0, 0x11, 0x22, 0x33, 0x44, @@ -96,6 +96,6 @@ describe('decode', () => { value: toU8a('0x9988') } ] - }) + }); }); }); diff --git a/packages/type-params/src/decode/index.ts b/packages/type-params/src/decode/index.ts index 821e83366f..db7759f018 100644 --- a/packages/type-params/src/decode/index.ts +++ b/packages/type-params/src/decode/index.ts @@ -5,10 +5,11 @@ import { EncodingVersions, Param$Decoded, Param$Type$Array, Param$Types, Param$Value, Param$Value$Array } from '../types'; import u8aToBn from '@polkadot/util/u8a/toBn'; +import toU8a from '@polkadot/util/u8a/toU8a'; import decodeValue from './value'; -function decodeTuple (type: Param$Type$Array, input: Uint8Array | null, version: EncodingVersions): Param$Decoded { +function decodeTuple (type: Param$Type$Array, input: Uint8Array | null, version: EncodingVersions, isStorage: boolean): Param$Decoded { if (!input) { return { length: 0, @@ -20,7 +21,7 @@ function decodeTuple (type: Param$Type$Array, input: Uint8Array | null, version: let length = 0; type.forEach((_type: Param$Types) => { - const decoded = decode(_type, input.subarray(length), version); + const decoded = decode(_type, input.subarray(length), version, isStorage); // NOTE small TS hack, since we don't have recursive arrays, assume primitives to push value.push(decoded.value as Param$Value); @@ -33,7 +34,7 @@ function decodeTuple (type: Param$Type$Array, input: Uint8Array | null, version: }; } -function decodeArray ([ type ]: Param$Type$Array, input: Uint8Array | null, version: EncodingVersions): Param$Decoded { +function decodeArray ([ type ]: Param$Type$Array, input: Uint8Array | null, version: EncodingVersions, isStorage: boolean): Param$Decoded { if (!input) { return { length: 0, @@ -46,7 +47,7 @@ function decodeArray ([ type ]: Param$Type$Array, input: Uint8Array | null, vers let length = 4; for (let index = 0; index < arrayLength; index++) { - const decoded = decode(type, input.subarray(length), version); + const decoded = decode(type, input.subarray(length), version, isStorage); // NOTE small TS hack, since we don't have recursive arrays, assume primitives to push value.push(decoded.value as Param$Value); @@ -59,15 +60,17 @@ function decodeArray ([ type ]: Param$Type$Array, input: Uint8Array | null, vers }; } -export default function decode (type: Param$Types, input: Uint8Array, version: EncodingVersions): Param$Decoded { +export default function decode (type: Param$Types, _input: Uint8Array | string | null, version: EncodingVersions, isStorage: boolean = false): Param$Decoded { + const input = toU8a(_input); + if (Array.isArray(type)) { // Arrays have single entries, Tuples will have multiple types if (type.length === 1) { - return decodeArray(type, input, version); + return decodeArray(type, input, version, isStorage); } else { - return decodeTuple(type, input, version); + return decodeTuple(type, input, version, isStorage); } } - return decodeValue(decode, type, input, version); + return decodeValue(decode, type, input, version, isStorage); } diff --git a/packages/type-params/src/decode/types.d.ts b/packages/type-params/src/decode/types.d.ts index 12f522f10c..b879e3468d 100644 --- a/packages/type-params/src/decode/types.d.ts +++ b/packages/type-params/src/decode/types.d.ts @@ -4,4 +4,4 @@ import { EncodingVersions, Param$Types, Param$Decoded } from '../types'; -export type Decoder = (type: Param$Types, input: Uint8Array, version: EncodingVersions) => Param$Decoded; +export type Decoder = (type: Param$Types, input: Uint8Array, version: EncodingVersions, isStorage: boolean) => Param$Decoded; diff --git a/packages/type-params/src/decode/value/bn.ts b/packages/type-params/src/decode/value/bn.ts index ad725a8f29..7c221e3695 100644 --- a/packages/type-params/src/decode/value/bn.ts +++ b/packages/type-params/src/decode/value/bn.ts @@ -8,12 +8,17 @@ import BN from 'bn.js'; import u8aToBn from '@polkadot/util/u8a/toBn'; export default function bn (input: Uint8Array | null, bitLength: 32 | 64 | 128): Param$Decoded { + if (input === null) { + return { + length: 0, + value: new BN(0) + }; + } + const length = bitLength / 8; return { length, - value: input - ? u8aToBn(input.subarray(0, length), true) - : new BN(0) + value: u8aToBn(input.subarray(0, length), true) }; } diff --git a/packages/type-params/src/decode/value/bool.ts b/packages/type-params/src/decode/value/bool.ts index 20b89befa7..53f940dfe7 100644 --- a/packages/type-params/src/decode/value/bool.ts +++ b/packages/type-params/src/decode/value/bool.ts @@ -5,8 +5,15 @@ import { Param$Decoded } from '../../types'; export default function bool (input: Uint8Array | null): Param$Decoded { + if (input === null) { + return { + length: 0, + value: false + }; + } + return { length: 1, - value: !!(input && input[0] !== 0) + value: input[0] !== 0 }; } diff --git a/packages/type-params/src/decode/value/byte.ts b/packages/type-params/src/decode/value/byte.ts index 837734c0fc..c4ffc2edef 100644 --- a/packages/type-params/src/decode/value/byte.ts +++ b/packages/type-params/src/decode/value/byte.ts @@ -5,10 +5,15 @@ import { Param$Decoded } from '../../types'; export default function byte (input: Uint8Array | null): Param$Decoded { + if (input === null) { + return { + length: 0, + value: 0 + }; + } + return { length: 1, - value: input - ? input[0] - : 0 + value: input[0] }; } diff --git a/packages/type-params/src/decode/value/bytes.ts b/packages/type-params/src/decode/value/bytes.ts index 5f1405f790..ba29800010 100644 --- a/packages/type-params/src/decode/value/bytes.ts +++ b/packages/type-params/src/decode/value/bytes.ts @@ -7,14 +7,17 @@ import { Param$Decoded } from '../../types'; import u8aToBn from '@polkadot/util/u8a/toBn'; export default function bytes (input: Uint8Array | null): Param$Decoded { - const length = input - ? u8aToBn(input.subarray(0, 4), true).toNumber() - : 0; + if (input === null) { + return { + length: 0, + value: new Uint8Array() + }; + } + + const length = u8aToBn(input.subarray(0, 4), true).toNumber(); return { length: length + 4, - value: input - ? input.subarray(4, length + 4) - : new Uint8Array() + value: input.subarray(4, length + 4) }; } diff --git a/packages/type-params/src/decode/value/code.ts b/packages/type-params/src/decode/value/code.ts index d89d040922..6b11039cf0 100644 --- a/packages/type-params/src/decode/value/code.ts +++ b/packages/type-params/src/decode/value/code.ts @@ -7,12 +7,15 @@ import { Param$Decoded } from '../../types'; import u8aToBn from '@polkadot/util/u8a/toBn'; export default function code (input: Uint8Array | null): Param$Decoded { - const length = input - ? u8aToBn(input.subarray(0, 4), true).toNumber() + 4 - : 0; - const value = input - ? input.subarray(4, length) - : new Uint8Array(); + if (input === null) { + return { + length: 0, + value: new Uint8Array() + }; + } + + const length = u8aToBn(input.subarray(0, 4), true).toNumber() + 4; + const value = input.subarray(4, length); return { length, diff --git a/packages/type-params/src/decode/value/digest.ts b/packages/type-params/src/decode/value/digest.ts index a5dbf192a1..0692218930 100644 --- a/packages/type-params/src/decode/value/digest.ts +++ b/packages/type-params/src/decode/value/digest.ts @@ -10,6 +10,12 @@ import u8aToBn from '@polkadot/util/u8a/toBn'; import bytes from './bytes'; export default function digest (input: Uint8Array | null): Param$Decoded { + if (input === null) { + return { + length: 0 + } as Param$Decoded; + } + const logLength = input ? u8aToBn(input.subarray(0, 4), true).toNumber() : 0; @@ -20,12 +26,7 @@ export default function digest (input: Uint8Array | null): Param$Decoded { let length = 4; for (let index = 0; index < logLength; index++) { - const decoded = input - ? bytes(input.subarray(length)) - : { - length: 0, - value: new Uint8Array() - }; + const decoded = bytes(input.subarray(length)); length += decoded.length; value.logs.push((decoded.value as Uint8Array)); diff --git a/packages/type-params/src/decode/value/header.ts b/packages/type-params/src/decode/value/header.ts index 87e59bc2a9..e9d812f0fa 100644 --- a/packages/type-params/src/decode/value/header.ts +++ b/packages/type-params/src/decode/value/header.ts @@ -7,10 +7,14 @@ import { Param$Decoded } from '../../types'; import decodeHeader from '@polkadot/primitives/codec/header/decode'; export default function header (input: Uint8Array | null): Param$Decoded { + if (input === null) { + return { + length: 0 + } as Param$Decoded; + } + // FIXME We don't have a way to determine the length atm - const length = input - ? input.length - : 0; + const length = input.length; const value = decodeHeader(input); return { diff --git a/packages/type-params/src/decode/value/index.ts b/packages/type-params/src/decode/value/index.ts index b0f8f6ab72..9d47dde454 100644 --- a/packages/type-params/src/decode/value/index.ts +++ b/packages/type-params/src/decode/value/index.ts @@ -6,6 +6,7 @@ import { EncodingVersions, Param$Decoded, Param$Type } from '../../types'; import { Decoder } from '../types'; import sizes from '@polkadot/primitives/sizes'; +import toU8a from '@polkadot/util/u8a/toU8a'; import bool from './bool'; import bn from './bn'; @@ -22,12 +23,14 @@ import string from './string'; import time from './time'; import u8a from './u8a'; -export default function decodeValue (decode: Decoder, type: Param$Type, input: Uint8Array, version: EncodingVersions): Param$Decoded { +export default function decodeValue (decode: Decoder, type: Param$Type, _input: Uint8Array | string | null, version: EncodingVersions, isStorage: boolean): Param$Decoded { + const input: Uint8Array | null = toU8a(_input); + try { switch (type) { // TODO Pass back the actual address, not publicKey? case 'AccountId': - return u8a(input, 256, version === 'poc-1' ? 0 : 1); + return u8a(input, 256, (isStorage || version === 'poc-1') ? 0 : 1); case 'Balance': return bn(input, sizes.Balance); @@ -42,11 +45,12 @@ export default function decodeValue (decode: Decoder, type: Param$Type, input: U return bool(input); case 'Bytes': + case 'StorageKey': return bytes(input); case 'Call': case 'Proposal': - return method(decode, input, type === 'Call', version); + return method(decode, input, type === 'Call', version, isStorage); case 'Code': return code(input); @@ -67,7 +71,7 @@ export default function decodeValue (decode: Decoder, type: Param$Type, input: U return bn(input, sizes.AccountIndex); case 'KeyValue': - case 'KeyValueStorage': + case 'StorageKeyValue': return keyValue(input); case 'MisbehaviorReport': @@ -95,6 +99,8 @@ export default function decodeValue (decode: Decoder, type: Param$Type, input: U case 'VoteThreshold': return byte(input); + case 'StorageResult': + case 'StorageResultSet': default: // tslint:disable-next-line (type as never); diff --git a/packages/type-params/src/decode/value/keyValue.spec.js b/packages/type-params/src/decode/value/keyValue.spec.js index 06b1158264..c6a94c2292 100644 --- a/packages/type-params/src/decode/value/keyValue.spec.js +++ b/packages/type-params/src/decode/value/keyValue.spec.js @@ -9,7 +9,7 @@ import decode from './index'; describe('keyValue', () => { const nullDecoder = () => {}; - it('encodes KeyValueStorage -> Uint8Array properly', () => { + it('encodes StorageKeyValue -> Uint8Array properly', () => { expect( decode( nullDecoder, @@ -28,7 +28,7 @@ describe('keyValue', () => { key: toU8a('0x11223344'), value: toU8a('0x998877665544332211') } - }) + }); }); it('encodes KeyValue -> Uint8Array properly', () => { @@ -50,6 +50,6 @@ describe('keyValue', () => { key: toU8a('0x11'), value: toU8a('0x9988') } - }) + }); }); }); diff --git a/packages/type-params/src/decode/value/keyValue.ts b/packages/type-params/src/decode/value/keyValue.ts index 0eb3eae9dd..cb81a73070 100644 --- a/packages/type-params/src/decode/value/keyValue.ts +++ b/packages/type-params/src/decode/value/keyValue.ts @@ -9,12 +9,8 @@ import u8aToBn from '@polkadot/util/u8a/toBn'; export default function bytes (input: Uint8Array | null): Param$Decoded { if (!input) { return { - length: 0, - value: { - key: new Uint8Array(), - value: new Uint8Array() - } - }; + length: 0 + } as Param$Decoded; } const keyLength = u8aToBn(input.subarray(0, 4), true).toNumber(); diff --git a/packages/type-params/src/decode/value/method.ts b/packages/type-params/src/decode/value/method.ts index 9002d092d5..08aee7b02c 100644 --- a/packages/type-params/src/decode/value/method.ts +++ b/packages/type-params/src/decode/value/method.ts @@ -16,7 +16,13 @@ type Result = { value: ExtrinsicDecoded }; -export default function call (decode: Decoder, input: Uint8Array, isPublic: boolean, version: EncodingVersions): Param$Decoded { +export default function call (decode: Decoder, input: Uint8Array | null, isPublic: boolean, version: EncodingVersions, isStorage: boolean): Param$Decoded { + if (input === null) { + return { + length: 0 + } as Param$Decoded; + } + const section: Section | undefined = Object.values(extrinsics).find(({ index }) => index[0] === input[0] ); @@ -42,7 +48,7 @@ export default function call (decode: Decoder, input: Uint8Array, isPublic: bool }; return params.reduce(({ length, value }: Result, { type }: Param) => { - const decoded = decode(type, input.subarray(length), version); + const decoded = decode(type, input.subarray(length), version, isStorage); value.params.push(decoded.value); diff --git a/packages/type-params/src/decode/value/misbehavior.ts b/packages/type-params/src/decode/value/misbehavior.ts index 34f5d1032b..4470430ceb 100644 --- a/packages/type-params/src/decode/value/misbehavior.ts +++ b/packages/type-params/src/decode/value/misbehavior.ts @@ -18,7 +18,13 @@ const B_SIG = B_HASH + 32; const length = B_SIG + 64; -export default function misbehavior (input: Uint8Array): Param$Decoded { +export default function misbehavior (input: Uint8Array | null): Param$Decoded { + if (input === null) { + return { + length: 0 + } as Param$Decoded; + } + return { length, value: ({ diff --git a/packages/type-params/src/decode/value/passThrough.ts b/packages/type-params/src/decode/value/passThrough.ts index 9a20abfbd8..6e46067663 100644 --- a/packages/type-params/src/decode/value/passThrough.ts +++ b/packages/type-params/src/decode/value/passThrough.ts @@ -4,7 +4,13 @@ import { Param$Decoded } from '../../types'; -export default function passThrough (input: Uint8Array): Param$Decoded { +export default function passThrough (input: Uint8Array | null): Param$Decoded { + if (input === null) { + return { + length: 0 + } as Param$Decoded; + } + return { length: input.length, value: input diff --git a/packages/type-params/src/decode/value/string.ts b/packages/type-params/src/decode/value/string.ts index 96a55848b6..9aace790dc 100644 --- a/packages/type-params/src/decode/value/string.ts +++ b/packages/type-params/src/decode/value/string.ts @@ -8,16 +8,19 @@ import u8aToBn from '@polkadot/util/u8a/toBn'; import u8aToUtf8 from '@polkadot/util/u8a/toUtf8'; export default function string (input: Uint8Array | null): Param$Decoded { - const length = input - ? u8aToBn(input.subarray(0, 4), true).toNumber() - : 0; + if (input === null) { + return { + length: 0, + value: '' + }; + } + + const length = u8aToBn(input.subarray(0, 4), true).toNumber(); return { length: length + 4, - value: input - ? u8aToUtf8( - input.subarray(4, length + 4) - ) - : '' + value: u8aToUtf8( + input.subarray(4, length + 4) + ) }; } diff --git a/packages/type-params/src/decode/value/time.ts b/packages/type-params/src/decode/value/time.ts index 365b5f4aac..579e87ea2b 100644 --- a/packages/type-params/src/decode/value/time.ts +++ b/packages/type-params/src/decode/value/time.ts @@ -7,14 +7,19 @@ import { Param$Decoded } from '../../types'; import u8aToBn from '@polkadot/util/u8a/toBn'; export default function time (input: Uint8Array | null): Param$Decoded { + if (input === null) { + return { + length: 0, + value: new Date(0) + }; + } + return { length: 8, - value: input - ? new Date( - u8aToBn(input.subarray(0, 8), true) - .imuln(1000) - .toNumber() - ) - : new Date(0) + value: new Date( + u8aToBn(input.subarray(0, 8), true) + .imuln(1000) + .toNumber() + ) }; } diff --git a/packages/type-params/src/decode/value/u8a.ts b/packages/type-params/src/decode/value/u8a.ts index e58cf85bf5..d094df6dc2 100644 --- a/packages/type-params/src/decode/value/u8a.ts +++ b/packages/type-params/src/decode/value/u8a.ts @@ -5,12 +5,17 @@ import { Param$Decoded } from '../../types'; export default function u8a (input: Uint8Array | null, bitLength: 256 | 512, offset: number): Param$Decoded { + if (input === null) { + return { + length: 0, + value: new Uint8Array() + }; + } + const length = (bitLength / 8) + offset; return { length, - value: input - ? input.subarray(offset, length) - : new Uint8Array() + value: input.subarray(offset, length) }; } diff --git a/packages/type-params/src/encode/type/index.ts b/packages/type-params/src/encode/type/index.ts index 88078cd77d..150c1ccf45 100644 --- a/packages/type-params/src/encode/type/index.ts +++ b/packages/type-params/src/encode/type/index.ts @@ -14,6 +14,7 @@ import defaultSizes from '@polkadot/primitives/sizes'; import sizes from '../../sizes'; import keyValue from './keyValue'; +import storageKey from './storageKey'; export default function encodeType (type: Param$Type, value: any, version: EncodingVersions): Uint8Array { try { @@ -60,7 +61,7 @@ export default function encodeType (type: Param$Type, value: any, version: Encod return bnToU8a(value, sizes.AccountIndex.get(version) || defaultSizes.Balance, true); case 'KeyValue': - case 'KeyValueStorage': + case 'StorageKeyValue': return keyValue(value); case 'ParachainId': @@ -70,6 +71,9 @@ export default function encodeType (type: Param$Type, value: any, version: Encod case 'u32': return bnToU8a(value, 32, true); + case 'StorageKey': + return storageKey(value); + case 'String': return (() => { const u8a = u8aFromString(value); diff --git a/packages/type-params/src/encode/type/keyValue.spec.js b/packages/type-params/src/encode/type/keyValue.spec.js index f63408b3de..d40f55a676 100644 --- a/packages/type-params/src/encode/type/keyValue.spec.js +++ b/packages/type-params/src/encode/type/keyValue.spec.js @@ -7,9 +7,9 @@ import toU8a from '@polkadot/util/u8a/toU8a'; import encode from './index'; describe('keyValue', () => { - it('encodes KeyValueStorage -> Uint8Array properly', () => { + it('encodes StorageKeyValue -> Uint8Array properly', () => { expect( - encode('KeyValueStorage', { + encode('StorageKeyValue', { key: toU8a('0x11223344'), value: toU8a('0x998877665544332211') }) @@ -20,7 +20,7 @@ describe('keyValue', () => { 9, 0, 0, 0, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 ]) - ) + ); }); it('encodes KeyValue -> Uint8Array properly', () => { @@ -36,6 +36,6 @@ describe('keyValue', () => { 2, 0, 0, 0, 0x99, 0x88 ]) - ) + ); }); }); diff --git a/packages/type-params/src/encode/type/storageKey.spec.js b/packages/type-params/src/encode/type/storageKey.spec.js new file mode 100644 index 0000000000..57f5614ecc --- /dev/null +++ b/packages/type-params/src/encode/type/storageKey.spec.js @@ -0,0 +1,21 @@ +// Copyright 2017-2018 @polkadot/params authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +import storage from '@polkadot/storage'; +import toU8a from '@polkadot/util/u8a/toU8a'; + +import encode from './index'; + +describe('storageKey', () => { + it('encodes storageKey -> Uint8Array properly', () => { + expect( + encode('StorageKey', [ + storage.staking.public.freeBalanceOf, + '5EhmTa7fL6SdjgKXo9g6hetR6nHnRAmrtisoGFWEESjzECtY' + ]) + ).toEqual( + toU8a('0x11cf1094db4db43356b7787c3e59c39f') + ); + }); +}); diff --git a/packages/type-params/src/encode/type/storageKey.ts b/packages/type-params/src/encode/type/storageKey.ts new file mode 100644 index 0000000000..f4e055fded --- /dev/null +++ b/packages/type-params/src/encode/type/storageKey.ts @@ -0,0 +1,12 @@ +// Copyright 2017-2018 @polkadot/params authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +import { SectionItem } from '@polkadot/params/types'; +import { Storages } from '@polkadot/storage/types'; + +import createStorageKey from '@polkadot/storage/key'; + +export default function storageKey ([key, ...params]: [SectionItem, any]): Uint8Array { + return createStorageKey(key).apply(null, params); +} diff --git a/packages/type-params/src/types.d.ts b/packages/type-params/src/types.d.ts index 905a2348a1..92ebc2e76d 100644 --- a/packages/type-params/src/types.d.ts +++ b/packages/type-params/src/types.d.ts @@ -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' | 'KeyValueStorage' | 'MisbehaviorReport' | 'ParachainId' | 'PropIndex' | 'Proposal' | 'ReferendumIndex' | 'SessionKey' | 'Signature' | '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' | 'MisbehaviorReport' | 'ParachainId' | 'PropIndex' | 'Proposal' | 'ReferendumIndex' | 'SessionKey' | 'Signature' | 'StorageKey' | 'StorageKeyValue' | 'StorageResult' | 'StorageResultSet' | 'String' | 'Timestamp' | 'u32' | 'u64' | 'u128' | 'VoteIndex' | 'VoteThreshold'; export type Param$Type$Array = Array>>; diff --git a/packages/type-storage/src/key/params.ts b/packages/type-storage/src/key/params.ts index a84c239b0c..30ca3b3d10 100644 --- a/packages/type-storage/src/key/params.ts +++ b/packages/type-storage/src/key/params.ts @@ -49,7 +49,10 @@ export default function formatParams (params: Params, values: Storage$Key$Value[ case 'Hash': case 'Header': case 'KeyValue': - case 'KeyValueStorage': + case 'StorageKey': + case 'StorageKeyValue': + case 'StorageResult': + case 'StorageResultSet': case 'MisbehaviorReport': case 'Proposal': case 'Signature':