From 535a9eac07bd2ca3aacea7ba7d31a2c75ea92bf7 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 17 Dec 2018 12:22:01 +0100 Subject: [PATCH] StorageKey with specific encoding (#480) * StorageKey with specific encoding * Comment typo --- packages/type-storage/src/substrate.ts | 4 +- .../src/utils/createFunction.spec.ts | 29 +++++++++- .../type-storage/src/utils/createFunction.ts | 58 +++++++++---------- packages/types/src/AccountIndex.ts | 13 +++-- packages/types/src/types.ts | 3 +- 5 files changed, 65 insertions(+), 42 deletions(-) diff --git a/packages/type-storage/src/substrate.ts b/packages/type-storage/src/substrate.ts index 4965281fbe..978ed5e4c9 100644 --- a/packages/type-storage/src/substrate.ts +++ b/packages/type-storage/src/substrate.ts @@ -18,7 +18,7 @@ interface SubstrateMetadata { const createRuntimeFunction = (method: string, key: string, { documentation, type }: SubstrateMetadata): StorageFunction => createFunction( new Text('Substrate'), - new Text(key), + new Text(method), { documentation: new Vector(Text, [documentation]), modifier: new StorageFunctionModifier(0), @@ -28,7 +28,7 @@ const createRuntimeFunction = (method: string, key: string, { documentation, typ } as StorageFunctionMetadata, { isUnhashed: true, - method + key } ); diff --git a/packages/type-storage/src/utils/createFunction.spec.ts b/packages/type-storage/src/utils/createFunction.spec.ts index f3ee7e4333..8803dba977 100644 --- a/packages/type-storage/src/utils/createFunction.spec.ts +++ b/packages/type-storage/src/utils/createFunction.spec.ts @@ -2,6 +2,8 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { stringToU8a, u8aConcat } from '@polkadot/util'; + import createFunction from './createFunction'; describe('createFunction', () => { @@ -17,17 +19,38 @@ describe('createFunction', () => { ); }); - it('allows overrides on method name', () => { + it('allows overrides on key (keeping name)', () => { expect( createFunction( 'Substrate', - ':auth:len', + 'authorityCount', { type: {} } as any, { isUnhashed: true, - method: 'authorityCount' + key: ':auth:len' } ).method ).toEqual('authorityCount'); }); + + it('allows overrides on key (unhashed)', () => { + const key = ':auth:len'; + + expect( + createFunction( + 'Substrate', + 'authorityCount', + { type: {} } as any, + { + isUnhashed: true, + key + } + )() + ).toEqual( + u8aConcat( + Uint8Array.from([key.length << 2]), + stringToU8a(':auth:len') + ) + ); + }); }); diff --git a/packages/type-storage/src/utils/createFunction.ts b/packages/type-storage/src/utils/createFunction.ts index 9036e51f75..eb0656c5a1 100644 --- a/packages/type-storage/src/utils/createFunction.ts +++ b/packages/type-storage/src/utils/createFunction.ts @@ -12,7 +12,7 @@ import { xxhashAsU8a } from '@polkadot/util-crypto'; export interface CreateItemOptions { isUnhashed?: boolean; - method?: string; + key?: string; } /** @@ -26,42 +26,38 @@ export interface CreateItemOptions { * by us manually at compile time. */ export default function createFunction (section: Text | string, method: Text | string, meta: StorageFunctionMetadata, options: CreateItemOptions = {}): StorageFunction { - let storageFn: any; - - // NOTE Here we assume everything in the 'Substrate' prefix is unhashed. (Despite not passing empty, i.e. '', - // the actual "prefix + name" below won't work even when we have an empty prefix.) For now, this is a safe - // assumption, but will break if the base substrate keys employ hashing as well - if (options.isUnhashed) { - storageFn = (): Uint8Array => - Compact.addLengthPrefix(stringToU8a(method.toString())); - } else { - // TODO Find better type than any - // Can only have zero or one argument: - // - storage.balances.freeBalance(address) - // - storage.timestamp.blockPeriod() - storageFn = (arg?: any): Uint8Array => { - const key = stringToU8a(`${section.toString()} ${method.toString()}`); - - if (!meta.type.isMap) { - return Compact.addLengthPrefix( - xxhashAsU8a(key, 128) - ); - } + // Can only have zero or one argument: + // - storage.balances.freeBalance(address) + // - storage.timestamp.blockPeriod() + const storageFn = (arg?: any): Uint8Array => { + let key = stringToU8a( + options.key + ? options.key + : `${section.toString()} ${method.toString()}` + ); + if (meta.type.isMap) { assert(!isUndefined(arg) && !isNull(arg), `${meta.name} expects one argument`); - const type = meta.type.asMap.key.toString(); // Argument type, as string - const param = createType(type, arg).toU8a(true); + const type = meta.type.asMap.key.toString(); - // StorageKey is a Bytes, so is length-prefixed - return Compact.addLengthPrefix( - xxhashAsU8a(u8aConcat(key, param), 128) - ); - }; - } + // encode with full encoding, indicating that this is a storage key to allow any + // specific encoding to take place (and example here is AccountIndex) + const param = createType(type, arg).toU8a(false, true); + + key = u8aConcat(key, param); + } + + // StorageKey is a Bytes, so is length-prefixed + return Compact.addLengthPrefix( + options.isUnhashed + ? key + : xxhashAsU8a(key, 128) + ); + }; storageFn.meta = meta; - storageFn.method = stringLowerFirst((options.method || method).toString()); + storageFn.method = stringLowerFirst(method.toString()); storageFn.section = stringLowerFirst(section.toString()); storageFn.toJSON = (): any => meta.toJSON(); diff --git a/packages/types/src/AccountIndex.ts b/packages/types/src/AccountIndex.ts index 0feaa03f8e..c99e23bea2 100644 --- a/packages/types/src/AccountIndex.ts +++ b/packages/types/src/AccountIndex.ts @@ -121,14 +121,17 @@ export default class AccountIndex extends U32 { /** * @description Encodes the value as a Uint8Array as per the parity-codec specifications * @param isBare true when the value has none of the type-specific prefixes (internal) + * @param isStorageKey true when encoded as part of a key, taking case of specific logic */ - toU8a (isBare?: boolean): Uint8Array { - // HACK 15 Oct 2018 For isBare assume that we are dealing with an AccountIndex + toU8a (isBare?: boolean, isStorageKey?: boolean): Uint8Array { + // HACK 15 Oct 2018 For isStorageKey assume that we are dealing with an AccountIndex // lookup (it is the only place where AccountIndex is used in such a manner to // construct a query). This is needed to get enumSet(AccountIndex) queries to // work in the way it was intended - return isBare - ? bnToU8a(this.div(ENUMSET_SIZE), 32, true) - : super.toU8a(); + if (isStorageKey) { + return bnToU8a(this.div(ENUMSET_SIZE), 32, true); + } + + return super.toU8a(isBare); } } diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 317bb2765f..03b11e478e 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -44,8 +44,9 @@ export interface Codec { /** * @description Encodes the value as a Uint8Array as per the parity-codec specifications * @param isBare true when the value has none of the type-specific prefixes (internal) + * @param isStorageKey true when encoded as part of a key, taking case of specific logic */ - toU8a (isBare?: boolean): Uint8Array; + toU8a (isBare?: boolean, isStorageKey?: boolean): Uint8Array; } export type CodecTo = 'toHex' | 'toJSON' | 'toString' | 'toU8a';